Compare commits
8 Commits
v0.0.37
...
charlie/cs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8bb0632c5 | ||
|
|
d7412af996 | ||
|
|
b1f734f445 | ||
|
|
a9d1d17eac | ||
|
|
0cfa2d617a | ||
|
|
21d39a9b44 | ||
|
|
9ad82a6127 | ||
|
|
4617c997bb |
28
.github/workflows/release.yaml
vendored
28
.github/workflows/release.yaml
vendored
@@ -1,8 +1,6 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
@@ -16,7 +14,7 @@ env:
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -39,25 +37,6 @@ jobs:
|
||||
- name: Install built wheel - x86_64
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - universal2
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
@@ -148,7 +127,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --no-default-features --release --out dist
|
||||
args: --release --out dist
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@v2.0.5
|
||||
if: matrix.target != 'ppc64'
|
||||
@@ -280,8 +259,7 @@ jobs:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- macos
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.37
|
||||
rev: v0.0.24
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
899
Cargo.lock
generated
899
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.37"
|
||||
version = "0.0.24"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "ruff"
|
||||
|
||||
[dependencies]
|
||||
libcst = { path = "../LibCST/native/libcst" }
|
||||
anyhow = { version = "1.0.60" }
|
||||
bincode = { version = "1.3.3" }
|
||||
cacache = { version = "10.0.1" }
|
||||
@@ -18,24 +19,19 @@ common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = "0.10.3"
|
||||
lazy_static = "1.4.0"
|
||||
glob = { version = "0.3.0"}
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1613f6c6990011a4bc559e79aaf28d715f9f729b" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
update-informer = ["dep:update-informer"]
|
||||
bumpalo = "3.11.0"
|
||||
bat = "0.21.0"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
130
README.md
130
README.md
@@ -17,9 +17,8 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🐍 Installable via `pip`
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
|
||||
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired `--fix` support
|
||||
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support
|
||||
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache semantics
|
||||
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` semantics
|
||||
|
||||
_ruff is a proof-of-concept and not yet intended for production use. It supports only a small subset
|
||||
of the Flake8 rules, and may crash on your codebase._
|
||||
@@ -57,7 +56,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.37
|
||||
rev: v0.0.24
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -86,7 +85,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.37)
|
||||
ruff
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -96,92 +95,17 @@ ARGS:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
--exclude <EXCLUDE>... List of file and/or directory patterns to exclude from checks
|
||||
-f, --fix Attempt to automatically fix lint errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... List of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... List of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... Comma-separated list of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... Comma-separated list of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
```
|
||||
|
||||
### Compatibility with Black
|
||||
|
||||
ruff is intended to be compatible with [Black](https://github.com/psf/black), and should be
|
||||
compatible out-of-the-box as long as the `line-length` setting is consistent between the two.
|
||||
|
||||
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing
|
||||
lint rules that are obviated by Black (e.g., stylistic rules).
|
||||
|
||||
### Parity with Flake8
|
||||
|
||||
ruff's goal is to achieve feature-parity with Flake8 when used (1) without any plugins,
|
||||
(2) alongside Black, and (3) on Python 3 code. (Using Black obviates the need for many of Flake8's
|
||||
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
||||
|
||||
Under those conditions, Flake8 implements about 60 rules, give or take. At time of writing, ruff
|
||||
implements 38 rules. (Note that these 38 rules likely cover a disproportionate share of errors:
|
||||
unused imports, undefined variables, etc.)
|
||||
|
||||
The unimplemented rules are tracked in #170, and include:
|
||||
|
||||
- 14 rules related to string `.format` calls.
|
||||
- 1 rule related to parsing and syntax.
|
||||
- 6 logical rules.
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Flake8 supports a wider range of `noqa` patterns, such as per-file ignores defined in `.flake8`.
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
3. ruff does not yet support parenthesized context managers.
|
||||
|
||||
## Rules
|
||||
|
||||
| Code | Name | Message |
|
||||
| ---- | ----- | ------- |
|
||||
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
|
||||
| E501 | LineTooLong | Line too long (89 > 88 characters) |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
|
||||
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
|
||||
| E713 | NotInTest | Test for membership should be `not in` |
|
||||
| E714 | NotIsTest | Test for object identity should be `is not` |
|
||||
| E722 | DoNotUseBareExcept | Do not use bare `except` |
|
||||
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
|
||||
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
|
||||
| E742 | AmbiguousClassName | ambiguous class name '...' |
|
||||
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
|
||||
| E902 | IOError | No such file or directory: `...` |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | `from ... import *` used; unable to detect undefined names |
|
||||
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
|
||||
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
|
||||
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
|
||||
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
|
||||
| F622 | TwoStarredExpressions | two starred expressions in assignment |
|
||||
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` |
|
||||
| F701 | BreakOutsideLoop | `break` outside loop |
|
||||
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
|
||||
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
|
||||
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
|
||||
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
|
||||
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
|
||||
| F821 | UndefinedName | Undefined name `...` |
|
||||
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
|
||||
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
|
||||
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
|
||||
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
|
||||
|
||||
## Development
|
||||
|
||||
ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
@@ -190,7 +114,7 @@ for development.
|
||||
Assuming you have `cargo` installed, you can run:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures
|
||||
cargo run resources/test/src
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
cargo test
|
||||
@@ -214,25 +138,49 @@ git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpy
|
||||
Add this `pyproject.toml` to the CPython directory:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
[tool.linter]
|
||||
line-length = 88
|
||||
exclude = [
|
||||
"Lib/ctypes/test/test_numbers.py",
|
||||
"Lib/dataclasses.py",
|
||||
"Lib/lib2to3/tests/data/bom.py",
|
||||
"Lib/lib2to3/tests/data/crlf.py",
|
||||
"Lib/lib2to3/tests/data/different_encoding.py",
|
||||
"Lib/lib2to3/tests/data/false_encoding.py",
|
||||
"Lib/lib2to3/tests/data/py2_test_grammar.py",
|
||||
"Lib/sqlite3/test/factory.py",
|
||||
"Lib/sqlite3/test/hooks.py",
|
||||
"Lib/sqlite3/test/regression.py",
|
||||
"Lib/sqlite3/test/transactions.py",
|
||||
"Lib/sqlite3/test/types.py",
|
||||
"Lib/test/bad_coding2.py",
|
||||
"Lib/test/badsyntax_3131.py",
|
||||
"Lib/test/badsyntax_pep3120.py",
|
||||
"Lib/test/encoded_modules/module_iso_8859_1.py",
|
||||
"Lib/test/encoded_modules/module_koi8_r.py",
|
||||
"Lib/test/sortperf.py",
|
||||
"Lib/test/test_email/torture_test.py",
|
||||
"Lib/test/test_fstring.py",
|
||||
"Lib/test/test_genericpath.py",
|
||||
"Lib/test/test_getopt.py",
|
||||
"Lib/test/test_grammar.py",
|
||||
"Lib/test/test_htmlparser.py",
|
||||
"Lib/test/test_importlib/stubs.py",
|
||||
"Lib/test/test_importlib/test_files.py",
|
||||
"Lib/test/test_importlib/test_metadata_api.py",
|
||||
"Lib/test/test_importlib/test_open.py",
|
||||
"Lib/test/test_importlib/test_util.py",
|
||||
"Lib/test/test_named_expressions.py",
|
||||
"Lib/test/test_patma.py",
|
||||
"Lib/test/test_peg_generator/__main__.py",
|
||||
"Lib/test/test_pipes.py",
|
||||
"Lib/test/test_source_encoding.py",
|
||||
"Lib/test/test_weakref.py",
|
||||
"Lib/test/test_webbrowser.py",
|
||||
"Lib/tkinter/__main__.py",
|
||||
"Lib/tkinter/test/test_tkinter/test_variables.py",
|
||||
"Modules/_decimal/libmpdec/literature/fnt.py",
|
||||
"Modules/_decimal/tests/deccheck.py",
|
||||
"Tools/c-analyzer/c_parser/parser/_delim.py",
|
||||
"Tools/i18n/pygettext.py",
|
||||
"Tools/test2to3/maintest.py",
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use ruff::checks::{CheckKind, RejectedCmpop};
|
||||
|
||||
fn main() {
|
||||
let mut check_kinds: Vec<CheckKind> = vec![
|
||||
CheckKind::AmbiguousClassName("...".to_string()),
|
||||
CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckKind::AmbiguousVariableName("...".to_string()),
|
||||
CheckKind::AssertTuple,
|
||||
CheckKind::BreakOutsideLoop,
|
||||
CheckKind::ContinueOutsideLoop,
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
CheckKind::DoNotAssignLambda,
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckKind::IOError("...".to_string()),
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckKind::ImportStarUsage("...".to_string()),
|
||||
CheckKind::LateFutureImport,
|
||||
CheckKind::LineTooLong(89, 88),
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
|
||||
CheckKind::NoAssertEquals,
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
CheckKind::NotInTest,
|
||||
CheckKind::NotIsTest,
|
||||
CheckKind::RaiseNotImplemented,
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
CheckKind::TooManyExpressionsInStarredAssignment,
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
CheckKind::TwoStarredExpressions,
|
||||
CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckKind::UndefinedName("...".to_string()),
|
||||
CheckKind::UnusedImport("...".to_string()),
|
||||
CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckKind::YieldOutsideFunction,
|
||||
];
|
||||
check_kinds.sort_by_key(|check_kind| check_kind.code());
|
||||
|
||||
println!("| Code | Name | Message |");
|
||||
println!("| ---- | ----- | ------- |");
|
||||
for check_kind in check_kinds {
|
||||
println!(
|
||||
"| {} | {} | {} |",
|
||||
check_kind.code().as_str(),
|
||||
check_kind.name(),
|
||||
check_kind.body()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/// Print the AST for a given Python file.
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
|
||||
println!("{:#?}", python_ast);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/// Print the token stream for a given Python file.
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
|
||||
println!("{:#?}", tok);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -19,12 +19,10 @@ classifiers = [
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
url = "https://github.com/charliermarsh/ruff"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.13,<0.14"]
|
||||
build-backend = "maturin"
|
||||
|
||||
10
remove_object_base.py
Normal file
10
remove_object_base.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
|
||||
class Bar(Foo, object):
|
||||
pass
|
||||
|
||||
|
||||
class Baz(Foo, Bar, object):
|
||||
pass
|
||||
1
remove_redundant_fstring.py
Normal file
1
remove_redundant_fstring.py
Normal file
@@ -0,0 +1 @@
|
||||
bad: str = f"bad" + "bad"
|
||||
28
resources/test/fixtures/E402.py
vendored
28
resources/test/fixtures/E402.py
vendored
@@ -1,28 +0,0 @@
|
||||
"""Top-level docstring."""
|
||||
import a
|
||||
|
||||
try:
|
||||
import b
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
import c
|
||||
|
||||
if x > 0:
|
||||
import d
|
||||
else:
|
||||
import e
|
||||
|
||||
y = x + 1
|
||||
|
||||
import f
|
||||
|
||||
|
||||
def foo() -> None:
|
||||
import e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import g
|
||||
11
resources/test/fixtures/E711.py
vendored
11
resources/test/fixtures/E711.py
vendored
@@ -1,11 +0,0 @@
|
||||
if var == None:
|
||||
pass
|
||||
|
||||
if None != var:
|
||||
pass
|
||||
|
||||
if var is None:
|
||||
pass
|
||||
|
||||
if None is not var:
|
||||
pass
|
||||
14
resources/test/fixtures/E712.py
vendored
14
resources/test/fixtures/E712.py
vendored
@@ -1,14 +0,0 @@
|
||||
if var == True:
|
||||
pass
|
||||
|
||||
if False != var:
|
||||
pass
|
||||
|
||||
if var != False != True:
|
||||
pass
|
||||
|
||||
if var is True:
|
||||
pass
|
||||
|
||||
if False is not var:
|
||||
pass
|
||||
7
resources/test/fixtures/E713.py
vendored
7
resources/test/fixtures/E713.py
vendored
@@ -1,7 +0,0 @@
|
||||
my_list = [1, 2, 3]
|
||||
if not num in my_list:
|
||||
print(num)
|
||||
|
||||
my_list = [1, 2, 3]
|
||||
if num not in my_list:
|
||||
print(num)
|
||||
5
resources/test/fixtures/E714.py
vendored
5
resources/test/fixtures/E714.py
vendored
@@ -1,5 +0,0 @@
|
||||
if not user is None:
|
||||
print(user.name)
|
||||
|
||||
if user is not None:
|
||||
print(user.name)
|
||||
9
resources/test/fixtures/E722.py
vendored
9
resources/test/fixtures/E722.py
vendored
@@ -1,9 +0,0 @@
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
6
resources/test/fixtures/E731.py
vendored
6
resources/test/fixtures/E731.py
vendored
@@ -1,6 +0,0 @@
|
||||
from typing import Callable, Iterable
|
||||
|
||||
a = lambda x: x**2
|
||||
b = map(lambda x: 2 * x, range(3))
|
||||
c: Callable = lambda x: x**2
|
||||
d: Iterable = map(lambda x: 2 * x, range(3))
|
||||
75
resources/test/fixtures/E741.py
vendored
75
resources/test/fixtures/E741.py
vendored
@@ -1,75 +0,0 @@
|
||||
from contextlib import contextmanager
|
||||
|
||||
l = 0
|
||||
I = 0
|
||||
O = 0
|
||||
l: int = 0
|
||||
|
||||
a, l = 0, 1
|
||||
[a, l] = 0, 1
|
||||
a, *l = 0, 1, 2
|
||||
a = l = 0
|
||||
|
||||
o = 0
|
||||
i = 0
|
||||
|
||||
for l in range(3):
|
||||
pass
|
||||
|
||||
|
||||
for a, l in zip(range(3), range(3)):
|
||||
pass
|
||||
|
||||
|
||||
def f1():
|
||||
global l
|
||||
l = 0
|
||||
|
||||
|
||||
def f2():
|
||||
l = 0
|
||||
|
||||
def f3():
|
||||
nonlocal l
|
||||
l = 1
|
||||
|
||||
f3()
|
||||
return l
|
||||
|
||||
|
||||
def f4(l, /, I):
|
||||
return l, I, O
|
||||
|
||||
|
||||
def f5(l=0, *, I=1):
|
||||
return l, I
|
||||
|
||||
|
||||
def f6(*l, **I):
|
||||
return l, I
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx1():
|
||||
yield 0
|
||||
|
||||
|
||||
with ctx1() as l:
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx2():
|
||||
yield 0, 1
|
||||
|
||||
|
||||
with ctx2() as (a, l):
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError as l:
|
||||
pass
|
||||
|
||||
if (l := 5) > 0:
|
||||
pass
|
||||
14
resources/test/fixtures/E742.py
vendored
14
resources/test/fixtures/E742.py
vendored
@@ -1,14 +0,0 @@
|
||||
class l:
|
||||
pass
|
||||
|
||||
|
||||
class I:
|
||||
pass
|
||||
|
||||
|
||||
class O:
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
pass
|
||||
15
resources/test/fixtures/E743.py
vendored
15
resources/test/fixtures/E743.py
vendored
@@ -1,15 +0,0 @@
|
||||
def l():
|
||||
pass
|
||||
|
||||
|
||||
def I():
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
def O(self):
|
||||
pass
|
||||
|
||||
|
||||
def x():
|
||||
pass
|
||||
7
resources/test/fixtures/F404.py
vendored
7
resources/test/fixtures/F404.py
vendored
@@ -1,7 +0,0 @@
|
||||
from __future__ import print_function
|
||||
"""Docstring"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from __future__ import print_function
|
||||
9
resources/test/fixtures/F406.py
vendored
9
resources/test/fixtures/F406.py
vendored
@@ -1,9 +0,0 @@
|
||||
from F634 import *
|
||||
|
||||
|
||||
def f():
|
||||
from F634 import *
|
||||
|
||||
|
||||
class F:
|
||||
from F634 import *
|
||||
2
resources/test/fixtures/F407.py
vendored
2
resources/test/fixtures/F407.py
vendored
@@ -1,2 +0,0 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import non_existent_feature
|
||||
12
resources/test/fixtures/F601.py
vendored
12
resources/test/fixtures/F601.py
vendored
@@ -1,12 +0,0 @@
|
||||
x = {
|
||||
"a": 1,
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
("a", "b"): 3,
|
||||
("a", "b"): 4,
|
||||
1.0: 2,
|
||||
1: 0,
|
||||
1: 3,
|
||||
b"123": 1,
|
||||
b"123": 4,
|
||||
}
|
||||
7
resources/test/fixtures/F602.py
vendored
7
resources/test/fixtures/F602.py
vendored
@@ -1,7 +0,0 @@
|
||||
a = 1
|
||||
b = 2
|
||||
x = {
|
||||
a: 1,
|
||||
a: 2,
|
||||
b: 3,
|
||||
}
|
||||
3
resources/test/fixtures/F622.py
vendored
3
resources/test/fixtures/F622.py
vendored
@@ -1,3 +0,0 @@
|
||||
*a, *b, c = (1, 2, 3)
|
||||
*a, b, c = (1, 2, 3)
|
||||
a, b, *c = (1, 2, 3)
|
||||
4
resources/test/fixtures/F631.py
vendored
4
resources/test/fixtures/F631.py
vendored
@@ -1,4 +0,0 @@
|
||||
assert (False, "x")
|
||||
assert (False,)
|
||||
assert ()
|
||||
assert True
|
||||
23
resources/test/fixtures/F701.py
vendored
23
resources/test/fixtures/F701.py
vendored
@@ -1,23 +0,0 @@
|
||||
for i in range(10):
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
i = 0
|
||||
while i < 10:
|
||||
i += 1
|
||||
break
|
||||
|
||||
|
||||
def f():
|
||||
for i in range(10):
|
||||
break
|
||||
|
||||
break
|
||||
|
||||
|
||||
class Foo:
|
||||
break
|
||||
|
||||
|
||||
break
|
||||
23
resources/test/fixtures/F702.py
vendored
23
resources/test/fixtures/F702.py
vendored
@@ -1,23 +0,0 @@
|
||||
for i in range(10):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
i = 0
|
||||
while i < 10:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
|
||||
def f():
|
||||
for i in range(10):
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
|
||||
class Foo:
|
||||
continue
|
||||
|
||||
|
||||
continue
|
||||
46
resources/test/fixtures/F707.py
vendored
46
resources/test/fixtures/F707.py
vendored
@@ -1,46 +0,0 @@
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
10
resources/test/fixtures/F722.py
vendored
10
resources/test/fixtures/F722.py
vendored
@@ -1,10 +0,0 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
|
||||
def f() -> "A":
|
||||
pass
|
||||
|
||||
|
||||
def g() -> "///":
|
||||
pass
|
||||
3
resources/test/fixtures/F822.py
vendored
3
resources/test/fixtures/F822.py
vendored
@@ -1,3 +0,0 @@
|
||||
a = 1
|
||||
|
||||
__all__ = ["a", "b"]
|
||||
141
resources/test/fixtures/R001.py
vendored
141
resources/test/fixtures/R001.py
vendored
@@ -1,141 +0,0 @@
|
||||
class A:
|
||||
...
|
||||
|
||||
|
||||
class A(object):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(A, object):
|
||||
...
|
||||
|
||||
|
||||
class B(object, A):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
object,
|
||||
A,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
A,
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
object,
|
||||
# Comment on A.
|
||||
A,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
# Comment on A.
|
||||
A,
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
class A(object):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object, # )
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object # )
|
||||
,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
object = A
|
||||
|
||||
|
||||
class B(object):
|
||||
...
|
||||
3
resources/test/fixtures/R002.py
vendored
3
resources/test/fixtures/R002.py
vendored
@@ -1,3 +0,0 @@
|
||||
self.assertEquals (1, 2)
|
||||
self.assertEquals(1, 2)
|
||||
self.assertEqual(3, 4)
|
||||
43
resources/test/fixtures/pyproject.toml
vendored
43
resources/test/fixtures/pyproject.toml
vendored
@@ -1,43 +0,0 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E402",
|
||||
"E501",
|
||||
"E711",
|
||||
"E712",
|
||||
"E713",
|
||||
"E714",
|
||||
"E722",
|
||||
"E731",
|
||||
"E741",
|
||||
"E742",
|
||||
"E743",
|
||||
"E902",
|
||||
"F401",
|
||||
"F403",
|
||||
"F404",
|
||||
"F406",
|
||||
"F407",
|
||||
"F541",
|
||||
"F601",
|
||||
"F602",
|
||||
"F621",
|
||||
"F622",
|
||||
"F631",
|
||||
"F634",
|
||||
"F701",
|
||||
"F702",
|
||||
"F704",
|
||||
"F706",
|
||||
"F707",
|
||||
"F722",
|
||||
"F821",
|
||||
"F822",
|
||||
"F823",
|
||||
"F831",
|
||||
"F841",
|
||||
"F901",
|
||||
"R001",
|
||||
"R002",
|
||||
]
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import all_feature_names
|
||||
import os
|
||||
import functools
|
||||
from datetime import datetime
|
||||
from collections import (
|
||||
Counter,
|
||||
OrderedDict,
|
||||
@@ -11,18 +10,11 @@ import multiprocessing.pool
|
||||
import multiprocessing.process
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
from typing import TYPING_CHECK, NamedTuple, Dict, Type, TypeVar, List, Set, Union, cast
|
||||
|
||||
from blah import ClassA, ClassB, ClassC
|
||||
|
||||
if TYPING_CHECK:
|
||||
from models import Fruit, Nut, Vegetable
|
||||
|
||||
|
||||
class X:
|
||||
datetime: datetime
|
||||
foo: Type["NamedTuple"]
|
||||
|
||||
def a(self) -> "namedtuple":
|
||||
x = os.environ["1"]
|
||||
y = Counter()
|
||||
@@ -31,11 +23,3 @@ class X:
|
||||
|
||||
__all__ = ["ClassA"] + ["ClassB"]
|
||||
__all__ += ["ClassC"]
|
||||
|
||||
X = TypeVar("X")
|
||||
Y = TypeVar("Y", bound="Dict")
|
||||
Z = TypeVar("Z", "List", "Set")
|
||||
|
||||
a = list["Fruit"]
|
||||
b = Union["Nut", None]
|
||||
c = cast("Vegetable", b)
|
||||
@@ -6,5 +6,7 @@ for _ in range(5):
|
||||
pass
|
||||
elif (3, 4):
|
||||
pass
|
||||
elif ():
|
||||
pass
|
||||
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
@@ -33,47 +33,14 @@ def ternary_optarg(prec, exp_range, itr):
|
||||
class Foo:
|
||||
CLASS_VAR = 1
|
||||
REFERENCES_CLASS_VAR = {"CLASS_VAR": CLASS_VAR}
|
||||
ANNOTATED_CLASS_VAR: int = 2
|
||||
|
||||
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Class:
|
||||
copy_on_model_validation: Literal["none", "deep", "shallow"]
|
||||
post_init_call: Literal["before_validation", "after_validation"]
|
||||
|
||||
def __init__(self):
|
||||
Class
|
||||
|
||||
# TODO(charlie): This should be recognized as a defined variable.
|
||||
Class # noqa: F821
|
||||
|
||||
try:
|
||||
x = 1 / 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
y: int = 1
|
||||
|
||||
x: "Bar" = 1
|
||||
|
||||
[first] = ["yup"]
|
||||
|
||||
|
||||
from typing import List, TypedDict
|
||||
|
||||
|
||||
class Item(TypedDict):
|
||||
nodes: List[TypedDict("Node", {"name": str})]
|
||||
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Ticket:
|
||||
class Status(Enum):
|
||||
OPEN = "OPEN"
|
||||
CLOSED = "CLOSED"
|
||||
|
||||
def set_status(self, status: Status):
|
||||
self.status = status
|
||||
@@ -14,11 +14,3 @@ def f():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
|
||||
|
||||
def g():
|
||||
foo = (1, 2)
|
||||
(a, b) = (1, 2)
|
||||
|
||||
bar = (1, 2)
|
||||
(c, d) = bar
|
||||
17
resources/test/src/pyproject.toml
Normal file
17
resources/test/src/pyproject.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E501",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
"F634",
|
||||
"F704",
|
||||
"F706",
|
||||
"F821",
|
||||
"F823",
|
||||
"F831",
|
||||
"F841",
|
||||
"F901",
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod checks;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
pub mod types;
|
||||
pub mod visitor;
|
||||
@@ -1,544 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::izip;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
|
||||
Location, Stmt, StmtKind, Unaryop,
|
||||
};
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::{Binding, BindingKind, Scope};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
pub fn check_if_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::IfTuple, location));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check AssertTuple compliance.
|
||||
pub fn check_assert_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::AssertTuple, location));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check NotInTest and NotIsTest compliance.
|
||||
pub fn check_not_tests(
|
||||
op: &Unaryop,
|
||||
operand: &Expr,
|
||||
check_not_in: bool,
|
||||
check_not_is: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
if matches!(op, Unaryop::Not) {
|
||||
if let ExprKind::Compare { ops, .. } = &operand.node {
|
||||
match ops[..] {
|
||||
[Cmpop::In] => {
|
||||
if check_not_in {
|
||||
checks.push(Check::new(CheckKind::NotInTest, operand.location));
|
||||
}
|
||||
}
|
||||
[Cmpop::Is] => {
|
||||
if check_not_is {
|
||||
checks.push(Check::new(CheckKind::NotIsTest, operand.location));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check UnusedVariable compliance.
|
||||
pub fn check_unused_variables(scope: &Scope) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
for (name, binding) in scope.values.iter() {
|
||||
// TODO(charlie): Ignore if using `locals`.
|
||||
if binding.used.is_none()
|
||||
&& name != "_"
|
||||
&& name != "__tracebackhide__"
|
||||
&& name != "__traceback_info__"
|
||||
&& name != "__traceback_supplement__"
|
||||
&& matches!(binding.kind, BindingKind::Assignment)
|
||||
{
|
||||
checks.push(Check::new(
|
||||
CheckKind::UnusedVariable(name.to_string()),
|
||||
binding.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check DoNotAssignLambda compliance.
|
||||
pub fn check_do_not_assign_lambda(value: &Expr, location: Location) -> Option<Check> {
|
||||
if let ExprKind::Lambda { .. } = &value.node {
|
||||
Some(Check::new(CheckKind::DoNotAssignLambda, location))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ambiguous_name(name: &str) -> bool {
|
||||
name == "l" || name == "I" || name == "O"
|
||||
}
|
||||
|
||||
/// Check AmbiguousVariableName compliance.
|
||||
pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousVariableName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousClassName compliance.
|
||||
pub fn check_ambiguous_class_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousClassName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousFunctionName compliance.
|
||||
pub fn check_ambiguous_function_name(name: &str, location: Location) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousFunctionName(name.to_string()),
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check UselessObjectInheritance compliance.
|
||||
pub fn check_useless_object_inheritance(
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
scope: &Scope,
|
||||
locator: &mut SourceCodeLocator,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Option<Check> {
|
||||
for expr in bases {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if id == "object" {
|
||||
match scope.values.get(id) {
|
||||
None
|
||||
| Some(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
}) => {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UselessObjectInheritance(name.to_string()),
|
||||
expr.location,
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate)
|
||||
|| matches!(autofix, fixer::Mode::Apply)
|
||||
{
|
||||
if let Some(fix) = fixes::remove_class_def_base(
|
||||
locator,
|
||||
&stmt.location,
|
||||
expr.location,
|
||||
bases,
|
||||
keywords,
|
||||
) {
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check DefaultExceptNotLast compliance.
|
||||
pub fn check_default_except_not_last(handlers: &Vec<Excepthandler>) -> Option<Check> {
|
||||
for (idx, handler) in handlers.iter().enumerate() {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
|
||||
if type_.is_none() && idx < handlers.len() - 1 {
|
||||
return Some(Check::new(
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
handler.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check RaiseNotImplemented compliance.
|
||||
pub fn check_raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(CheckKind::RaiseNotImplemented, expr.location));
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(CheckKind::RaiseNotImplemented, expr.location));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check DuplicateArgumentName compliance.
|
||||
pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Collect all the arguments into a single vector.
|
||||
let mut all_arguments: Vec<&Arg> = arguments
|
||||
.args
|
||||
.iter()
|
||||
.chain(arguments.posonlyargs.iter())
|
||||
.chain(arguments.kwonlyargs.iter())
|
||||
.collect();
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
all_arguments.push(arg);
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
all_arguments.push(arg);
|
||||
}
|
||||
|
||||
// Search for duplicates.
|
||||
let mut idents: BTreeSet<&str> = BTreeSet::new();
|
||||
for arg in all_arguments {
|
||||
let ident = &arg.node.arg;
|
||||
if idents.contains(ident.as_str()) {
|
||||
checks.push(Check::new(CheckKind::DuplicateArgumentName, arg.location));
|
||||
}
|
||||
idents.insert(ident);
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check AssertEquals compliance.
|
||||
pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check> {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
|
||||
if attr == "assertEquals" {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "self" {
|
||||
let mut check = Check::new(CheckKind::NoAssertEquals, expr.location);
|
||||
if matches!(autofix, fixer::Mode::Generate)
|
||||
|| matches!(autofix, fixer::Mode::Apply)
|
||||
{
|
||||
check.amend(Fix {
|
||||
content: "assertEqual".to_string(),
|
||||
start: Location::new(expr.location.row(), expr.location.column() + 1),
|
||||
end: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1 + "assertEquals".len(),
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum DictionaryKey<'a> {
|
||||
Constant(&'a Constant),
|
||||
Variable(&'a String),
|
||||
}
|
||||
|
||||
fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
|
||||
match &expr.node {
|
||||
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value)),
|
||||
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check MultiValueRepeatedKeyLiteral and MultiValueRepeatedKeyVariable compliance.
|
||||
pub fn check_repeated_keys(
|
||||
keys: &Vec<Expr>,
|
||||
check_repeated_literals: bool,
|
||||
check_repeated_variables: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
let num_keys = keys.len();
|
||||
for i in 0..num_keys {
|
||||
let k1 = &keys[i];
|
||||
let v1 = convert_to_value(k1);
|
||||
for k2 in keys.iter().take(num_keys).skip(i + 1) {
|
||||
let v2 = convert_to_value(k2);
|
||||
match (&v1, &v2) {
|
||||
(Some(DictionaryKey::Constant(v1)), Some(DictionaryKey::Constant(v2))) => {
|
||||
if check_repeated_literals && v1 == v2 {
|
||||
checks.push(Check::new(
|
||||
CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
k2.location,
|
||||
))
|
||||
}
|
||||
}
|
||||
(Some(DictionaryKey::Variable(v1)), Some(DictionaryKey::Variable(v2))) => {
|
||||
if check_repeated_variables && v1 == v2 {
|
||||
checks.push(Check::new(
|
||||
CheckKind::MultiValueRepeatedKeyVariable(v2.to_string()),
|
||||
k2.location,
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check TrueFalseComparison and NoneComparison compliance.
|
||||
pub fn check_literal_comparisons(
|
||||
left: &Expr,
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
check_none_comparisons: bool,
|
||||
check_true_false_comparisons: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
let op = ops.first().unwrap();
|
||||
let comparator = left;
|
||||
|
||||
// Check `left`.
|
||||
if check_none_comparisons
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if check_true_false_comparisons {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each comparator in order.
|
||||
for (op, comparator) in izip!(ops, comparators) {
|
||||
if check_none_comparisons
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if check_true_false_comparisons {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
|
||||
pub fn check_starred_expressions(
|
||||
elts: &[Expr],
|
||||
location: Location,
|
||||
check_too_many_expressions: bool,
|
||||
check_two_starred_expressions: bool,
|
||||
) -> Option<Check> {
|
||||
let mut has_starred: bool = false;
|
||||
let mut starred_index: Option<usize> = None;
|
||||
for (index, elt) in elts.iter().enumerate() {
|
||||
if matches!(elt.node, ExprKind::Starred { .. }) {
|
||||
if has_starred && check_two_starred_expressions {
|
||||
return Some(Check::new(CheckKind::TwoStarredExpressions, location));
|
||||
}
|
||||
has_starred = true;
|
||||
starred_index = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
if check_too_many_expressions {
|
||||
if let Some(starred_index) = starred_index {
|
||||
if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 {
|
||||
return Some(Check::new(
|
||||
CheckKind::TooManyExpressionsInStarredAssignment,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check BreakOutsideLoop compliance.
|
||||
pub fn check_break_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
) -> Option<Check> {
|
||||
let mut allowed: bool = false;
|
||||
let mut parent = stmt;
|
||||
for index in parent_stack.iter().rev() {
|
||||
let child = parent;
|
||||
parent = parents[*index];
|
||||
match &parent.node {
|
||||
StmtKind::For { orelse, .. }
|
||||
| StmtKind::AsyncFor { orelse, .. }
|
||||
| StmtKind::While { orelse, .. } => {
|
||||
if !orelse.contains(child) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
| StmtKind::ClassDef { .. } => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
Some(Check::new(CheckKind::BreakOutsideLoop, stmt.location))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check ContinueOutsideLoop compliance.
|
||||
pub fn check_continue_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
) -> Option<Check> {
|
||||
let mut allowed: bool = false;
|
||||
let mut parent = stmt;
|
||||
for index in parent_stack.iter().rev() {
|
||||
let child = parent;
|
||||
parent = parents[*index];
|
||||
match &parent.node {
|
||||
StmtKind::For { orelse, .. }
|
||||
| StmtKind::AsyncFor { orelse, .. }
|
||||
| StmtKind::While { orelse, .. } => {
|
||||
if !orelse.contains(child) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
| StmtKind::ClassDef { .. } => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
Some(Check::new(CheckKind::ContinueOutsideLoop, stmt.location))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::{BindingKind, Scope};
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
|
||||
for elt in elts {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &elt.node
|
||||
{
|
||||
names.push(value.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the existing bound __all__ values.
|
||||
if let StmtKind::AugAssign { .. } = &stmt.node {
|
||||
if let Some(binding) = scope.values.get("__all__") {
|
||||
if let BindingKind::Export(existing) = &binding.kind {
|
||||
names.extend(existing.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = match &stmt.node {
|
||||
StmtKind::Assign { value, .. } => Some(value),
|
||||
StmtKind::AnnAssign { value, .. } => value.as_ref(),
|
||||
StmtKind::AugAssign { value, .. } => Some(value),
|
||||
_ => None,
|
||||
} {
|
||||
match &value.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts)
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
let mut current_left = left;
|
||||
let mut current_right = right;
|
||||
while let Some(elts) = match ¤t_right.node {
|
||||
ExprKind::List { elts, .. } => Some(elts),
|
||||
ExprKind::Tuple { elts, .. } => Some(elts),
|
||||
_ => None,
|
||||
} {
|
||||
add_to_names(&mut names, elts);
|
||||
match ¤t_left.node {
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
current_left = left;
|
||||
current_right = right;
|
||||
}
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts);
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::While { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if let StmtKind::Expr { value } = &parent.node {
|
||||
if matches!(value.node, ExprKind::IfExp { .. }) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a node is in a nested block.
|
||||
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::Try { .. })
|
||||
|| matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::With { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
|
||||
for child in targets {
|
||||
match &child.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => {}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
match &value.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
|
||||
_ => {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||
pub struct SourceCodeLocator<'a> {
|
||||
content: &'a str,
|
||||
offsets: Vec<usize>,
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl<'a> SourceCodeLocator<'a> {
|
||||
pub fn new(content: &'a str) -> Self {
|
||||
SourceCodeLocator {
|
||||
content,
|
||||
offsets: vec![],
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice_source_code(&mut self, location: &Location) -> &'a str {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for i in self.content.lines() {
|
||||
self.offsets.push(offset);
|
||||
offset += i.len();
|
||||
offset += 1;
|
||||
}
|
||||
self.initialized = true;
|
||||
}
|
||||
let offset = self.offsets[location.row() - 1] + location.column() - 1;
|
||||
&self.content[offset..]
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Keyword, Location};
|
||||
|
||||
fn relocate_keyword(keyword: &mut Keyword, location: Location) {
|
||||
keyword.location = location;
|
||||
relocate_expr(&mut keyword.node.value, location);
|
||||
}
|
||||
|
||||
/// Change an expression's location (recursively) to match a desired, fixed location.
|
||||
pub fn relocate_expr(expr: &mut Expr, location: Location) {
|
||||
expr.location = location;
|
||||
match &mut expr.node {
|
||||
ExprKind::BoolOp { values, .. } => {
|
||||
for expr in values {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::NamedExpr { target, value } => {
|
||||
relocate_expr(target, location);
|
||||
relocate_expr(value, location);
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
relocate_expr(left, location);
|
||||
relocate_expr(right, location);
|
||||
}
|
||||
ExprKind::UnaryOp { operand, .. } => {
|
||||
relocate_expr(operand, location);
|
||||
}
|
||||
ExprKind::Lambda { body, .. } => {
|
||||
relocate_expr(body, location);
|
||||
}
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
relocate_expr(test, location);
|
||||
relocate_expr(body, location);
|
||||
relocate_expr(orelse, location);
|
||||
}
|
||||
ExprKind::Dict { keys, values } => {
|
||||
for expr in keys {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
for expr in values {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::Set { elts } => {
|
||||
for expr in elts {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::ListComp { elt, .. } => {
|
||||
relocate_expr(elt, location);
|
||||
}
|
||||
ExprKind::SetComp { elt, .. } => {
|
||||
relocate_expr(elt, location);
|
||||
}
|
||||
ExprKind::DictComp { key, value, .. } => {
|
||||
relocate_expr(key, location);
|
||||
relocate_expr(value, location);
|
||||
}
|
||||
ExprKind::GeneratorExp { elt, .. } => {
|
||||
relocate_expr(elt, location);
|
||||
}
|
||||
ExprKind::Await { value } => relocate_expr(value, location),
|
||||
ExprKind::Yield { value } => {
|
||||
if let Some(expr) = value {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::YieldFrom { value } => relocate_expr(value, location),
|
||||
ExprKind::Compare {
|
||||
left, comparators, ..
|
||||
} => {
|
||||
relocate_expr(left, location);
|
||||
for expr in comparators {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
relocate_expr(func, location);
|
||||
for expr in args {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
for keyword in keywords {
|
||||
relocate_keyword(keyword, location);
|
||||
}
|
||||
}
|
||||
ExprKind::FormattedValue {
|
||||
value, format_spec, ..
|
||||
} => {
|
||||
relocate_expr(value, location);
|
||||
if let Some(expr) = format_spec {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
for expr in values {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { .. } => {}
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
relocate_expr(value, location);
|
||||
}
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
relocate_expr(value, location);
|
||||
relocate_expr(slice, location);
|
||||
}
|
||||
ExprKind::Starred { value, .. } => {
|
||||
relocate_expr(value, location);
|
||||
}
|
||||
ExprKind::Name { .. } => {}
|
||||
ExprKind::List { elts, .. } => {
|
||||
for expr in elts {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
for expr in elts {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
if let Some(expr) = lower {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
if let Some(expr) = upper {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
if let Some(expr) = step {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Scope {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(kind: ScopeKind) -> Self {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
values: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BindingKind {
|
||||
Annotation,
|
||||
Argument,
|
||||
Assignment,
|
||||
Binding,
|
||||
Builtin,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
Importation(String),
|
||||
StarImportation,
|
||||
SubmoduleImportation(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Binding {
|
||||
pub kind: BindingKind,
|
||||
pub location: Location,
|
||||
pub used: Option<usize>,
|
||||
}
|
||||
117
src/ast_ops.rs
Normal file
117
src/ast_ops.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
pub struct Scope {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(kind: ScopeKind) -> Self {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
values: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
Builtin,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
Importation(String),
|
||||
StarImportation,
|
||||
SubmoduleImportation(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Binding {
|
||||
pub kind: BindingKind,
|
||||
pub location: Location,
|
||||
pub used: Option<usize>,
|
||||
}
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
|
||||
for elt in elts {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &elt.node
|
||||
{
|
||||
names.push(value.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the existing bound __all__ values.
|
||||
if let StmtKind::AugAssign { .. } = &stmt.node {
|
||||
if let Some(binding) = scope.values.get("__all__") {
|
||||
if let BindingKind::Export(existing) = &binding.kind {
|
||||
names.extend(existing.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = match &stmt.node {
|
||||
StmtKind::Assign { value, .. } => Some(value),
|
||||
StmtKind::AnnAssign { value, .. } => value.as_ref(),
|
||||
StmtKind::AugAssign { value, .. } => Some(value),
|
||||
_ => None,
|
||||
} {
|
||||
match &value.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts)
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
let mut current_left = left;
|
||||
let mut current_right = right;
|
||||
while let Some(elts) = match ¤t_right.node {
|
||||
ExprKind::List { elts, .. } => Some(elts),
|
||||
ExprKind::Tuple { elts, .. } => Some(elts),
|
||||
_ => None,
|
||||
} {
|
||||
add_to_names(&mut names, elts);
|
||||
match ¤t_left.node {
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
current_left = left;
|
||||
current_right = right;
|
||||
}
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts);
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
@@ -4,141 +4,102 @@ use rustpython_parser::ast::{
|
||||
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
|
||||
};
|
||||
|
||||
pub trait Visitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
pub trait ASTVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
fn visit_annotation(&mut self, expr: &'a Expr) {
|
||||
fn visit_annotation(&mut self, expr: &Expr) {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
fn visit_expr(&mut self, expr: &Expr, _parent: Option<&Stmt>) {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
fn visit_constant(&mut self, constant: &'a Constant) {
|
||||
fn visit_constant(&mut self, constant: &Constant) {
|
||||
walk_constant(self, constant);
|
||||
}
|
||||
fn visit_expr_context(&mut self, expr_content: &'a ExprContext) {
|
||||
fn visit_expr_context(&mut self, expr_content: &ExprContext) {
|
||||
walk_expr_context(self, expr_content);
|
||||
}
|
||||
fn visit_boolop(&mut self, boolop: &'a Boolop) {
|
||||
fn visit_boolop(&mut self, boolop: &Boolop) {
|
||||
walk_boolop(self, boolop);
|
||||
}
|
||||
fn visit_operator(&mut self, operator: &'a Operator) {
|
||||
fn visit_operator(&mut self, operator: &Operator) {
|
||||
walk_operator(self, operator);
|
||||
}
|
||||
fn visit_unaryop(&mut self, unaryop: &'a Unaryop) {
|
||||
fn visit_unaryop(&mut self, unaryop: &Unaryop) {
|
||||
walk_unaryop(self, unaryop);
|
||||
}
|
||||
fn visit_cmpop(&mut self, cmpop: &'a Cmpop) {
|
||||
fn visit_cmpop(&mut self, cmpop: &Cmpop) {
|
||||
walk_cmpop(self, cmpop);
|
||||
}
|
||||
fn visit_comprehension(&mut self, comprehension: &'a Comprehension) {
|
||||
fn visit_comprehension(&mut self, comprehension: &Comprehension) {
|
||||
walk_comprehension(self, comprehension);
|
||||
}
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'a Excepthandler) {
|
||||
fn visit_excepthandler(&mut self, excepthandler: &Excepthandler) {
|
||||
walk_excepthandler(self, excepthandler);
|
||||
}
|
||||
fn visit_arguments(&mut self, arguments: &'a Arguments) {
|
||||
fn visit_arguments(&mut self, arguments: &Arguments) {
|
||||
walk_arguments(self, arguments);
|
||||
}
|
||||
fn visit_arg(&mut self, arg: &'a Arg) {
|
||||
fn visit_arg(&mut self, arg: &Arg) {
|
||||
walk_arg(self, arg);
|
||||
}
|
||||
fn visit_keyword(&mut self, keyword: &'a Keyword) {
|
||||
fn visit_keyword(&mut self, keyword: &Keyword) {
|
||||
walk_keyword(self, keyword);
|
||||
}
|
||||
fn visit_alias(&mut self, alias: &'a Alias) {
|
||||
fn visit_alias(&mut self, alias: &Alias) {
|
||||
walk_alias(self, alias);
|
||||
}
|
||||
fn visit_withitem(&mut self, withitem: &'a Withitem) {
|
||||
fn visit_withitem(&mut self, withitem: &Withitem) {
|
||||
walk_withitem(self, withitem);
|
||||
}
|
||||
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
|
||||
fn visit_match_case(&mut self, match_case: &MatchCase) {
|
||||
walk_match_case(self, match_case);
|
||||
}
|
||||
fn visit_pattern(&mut self, pattern: &'a Pattern) {
|
||||
fn visit_pattern(&mut self, pattern: &Pattern) {
|
||||
walk_pattern(self, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
pub fn walk_stmt<V: ASTVisitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
StmtKind::FunctionDef { args, body, .. } => {
|
||||
visitor.visit_arguments(args);
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
for expr in returns {
|
||||
visitor.visit_annotation(expr);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::AsyncFunctionDef {
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
StmtKind::AsyncFunctionDef { args, body, .. } => {
|
||||
visitor.visit_arguments(args);
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
for expr in returns {
|
||||
visitor.visit_annotation(expr);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef {
|
||||
bases,
|
||||
keywords,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
for expr in bases {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
for keyword in keywords {
|
||||
visitor.visit_keyword(keyword);
|
||||
}
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::Return { value } => {
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { targets } => {
|
||||
for expr in targets {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
visitor.visit_expr(value);
|
||||
for expr in targets {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
visitor.visit_expr(value, Some(stmt))
|
||||
}
|
||||
StmtKind::AugAssign { target, op, value } => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_operator(op);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, Some(stmt));
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
@@ -146,11 +107,11 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
value,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_annotation(annotation);
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
visitor.visit_expr(target);
|
||||
}
|
||||
StmtKind::For {
|
||||
target,
|
||||
@@ -159,13 +120,13 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
orelse,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(iter);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_expr(iter, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::AsyncFor {
|
||||
@@ -175,31 +136,31 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
orelse,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(iter);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_expr(iter, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::While { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::If { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::With { items, body, .. } => {
|
||||
@@ -207,7 +168,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
visitor.visit_withitem(withitem);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::AsyncWith { items, body, .. } => {
|
||||
@@ -215,22 +176,22 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
visitor.visit_withitem(withitem);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::Match { subject, cases } => {
|
||||
// TODO(charlie): Handle `cases`.
|
||||
visitor.visit_expr(subject);
|
||||
visitor.visit_expr(subject, Some(stmt));
|
||||
for match_case in cases {
|
||||
visitor.visit_match_case(match_case);
|
||||
}
|
||||
}
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
if let Some(expr) = exc {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
};
|
||||
if let Some(expr) = cause {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
};
|
||||
}
|
||||
StmtKind::Try {
|
||||
@@ -240,22 +201,22 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
finalbody,
|
||||
} => {
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for excepthandler in handlers {
|
||||
visitor.visit_excepthandler(excepthandler)
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for stmt in finalbody {
|
||||
visitor.visit_stmt(stmt);
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, msg } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, None);
|
||||
if let Some(expr) = msg {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
@@ -270,67 +231,67 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
}
|
||||
StmtKind::Global { .. } => {}
|
||||
StmtKind::Nonlocal { .. } => {}
|
||||
StmtKind::Expr { value } => visitor.visit_expr(value),
|
||||
StmtKind::Expr { value } => visitor.visit_expr(value, Some(stmt)),
|
||||
StmtKind::Pass => {}
|
||||
StmtKind::Break => {}
|
||||
StmtKind::Continue => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
pub fn walk_expr<V: ASTVisitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::BoolOp { op, values } => {
|
||||
visitor.visit_boolop(op);
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::NamedExpr { target, value } => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(target, None);
|
||||
visitor.visit_expr(value, None);
|
||||
}
|
||||
ExprKind::BinOp { left, op, right } => {
|
||||
visitor.visit_expr(left);
|
||||
visitor.visit_expr(left, None);
|
||||
visitor.visit_operator(op);
|
||||
visitor.visit_expr(right);
|
||||
visitor.visit_expr(right, None);
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
visitor.visit_unaryop(op);
|
||||
visitor.visit_expr(operand);
|
||||
visitor.visit_expr(operand, None);
|
||||
}
|
||||
ExprKind::Lambda { args, body } => {
|
||||
visitor.visit_arguments(args);
|
||||
visitor.visit_expr(body);
|
||||
visitor.visit_expr(body, None);
|
||||
}
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(body);
|
||||
visitor.visit_expr(orelse);
|
||||
visitor.visit_expr(test, None);
|
||||
visitor.visit_expr(body, None);
|
||||
visitor.visit_expr(orelse, None);
|
||||
}
|
||||
ExprKind::Dict { keys, values } => {
|
||||
for expr in keys {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Set { elts } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::ListComp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::SetComp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
@@ -338,35 +299,35 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
generators,
|
||||
} => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(key);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(key, None);
|
||||
visitor.visit_expr(value, None);
|
||||
}
|
||||
ExprKind::GeneratorExp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::Await { value } => visitor.visit_expr(value),
|
||||
ExprKind::Await { value } => visitor.visit_expr(value, None),
|
||||
ExprKind::Yield { value } => {
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::YieldFrom { value } => visitor.visit_expr(value),
|
||||
ExprKind::YieldFrom { value } => visitor.visit_expr(value, None),
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} => {
|
||||
visitor.visit_expr(left);
|
||||
visitor.visit_expr(left, None);
|
||||
for cmpop in ops {
|
||||
visitor.visit_cmpop(cmpop);
|
||||
}
|
||||
for expr in comparators {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
@@ -374,9 +335,9 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
visitor.visit_expr(func);
|
||||
visitor.visit_expr(func, None);
|
||||
for expr in args {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for keyword in keywords {
|
||||
visitor.visit_keyword(keyword);
|
||||
@@ -385,28 +346,28 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
ExprKind::FormattedValue {
|
||||
value, format_spec, ..
|
||||
} => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
if let Some(expr) = format_spec {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { value, .. } => visitor.visit_constant(value),
|
||||
ExprKind::Attribute { value, ctx, .. } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(slice);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr(slice, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Starred { value, ctx } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Name { ctx, .. } => {
|
||||
@@ -414,31 +375,31 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
}
|
||||
ExprKind::List { elts, ctx } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Tuple { elts, ctx } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
if let Some(expr) = lower {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
if let Some(expr) = upper {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
if let Some(expr) = step {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a Constant) {
|
||||
pub fn walk_constant<V: ASTVisitor + ?Sized>(visitor: &mut V, constant: &Constant) {
|
||||
if let Constant::Tuple(constants) = constant {
|
||||
for constant in constants {
|
||||
visitor.visit_constant(constant)
|
||||
@@ -446,25 +407,19 @@ pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_comprehension<'a, V: Visitor<'a> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
comprehension: &'a Comprehension,
|
||||
) {
|
||||
visitor.visit_expr(&comprehension.target);
|
||||
visitor.visit_expr(&comprehension.iter);
|
||||
pub fn walk_comprehension<V: ASTVisitor + ?Sized>(visitor: &mut V, comprehension: &Comprehension) {
|
||||
visitor.visit_expr(&comprehension.target, None);
|
||||
visitor.visit_expr(&comprehension.iter, None);
|
||||
for expr in &comprehension.ifs {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_excepthandler<'a, V: Visitor<'a> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
excepthandler: &'a Excepthandler,
|
||||
) {
|
||||
pub fn walk_excepthandler<V: ASTVisitor + ?Sized>(visitor: &mut V, excepthandler: &Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, body, .. } => {
|
||||
if let Some(expr) = type_ {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
@@ -473,7 +428,7 @@ pub fn walk_excepthandler<'a, V: Visitor<'a> + ?Sized>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
|
||||
pub fn walk_arguments<V: ASTVisitor + ?Sized>(visitor: &mut V, arguments: &Arguments) {
|
||||
for arg in &arguments.posonlyargs {
|
||||
visitor.visit_arg(arg);
|
||||
}
|
||||
@@ -481,61 +436,61 @@ pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &
|
||||
visitor.visit_arg(arg);
|
||||
}
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
visitor.visit_arg(arg);
|
||||
visitor.visit_arg(arg)
|
||||
}
|
||||
for arg in &arguments.kwonlyargs {
|
||||
visitor.visit_arg(arg);
|
||||
}
|
||||
for expr in &arguments.kw_defaults {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
visitor.visit_arg(arg);
|
||||
visitor.visit_arg(arg)
|
||||
}
|
||||
for expr in &arguments.defaults {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_arg<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arg: &'a Arg) {
|
||||
pub fn walk_arg<V: ASTVisitor + ?Sized>(visitor: &mut V, arg: &Arg) {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
visitor.visit_annotation(expr);
|
||||
visitor.visit_annotation(expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_keyword<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, keyword: &'a Keyword) {
|
||||
visitor.visit_expr(&keyword.node.value);
|
||||
pub fn walk_keyword<V: ASTVisitor + ?Sized>(visitor: &mut V, keyword: &Keyword) {
|
||||
visitor.visit_expr(&keyword.node.value, None);
|
||||
}
|
||||
|
||||
pub fn walk_withitem<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, withitem: &'a Withitem) {
|
||||
visitor.visit_expr(&withitem.context_expr);
|
||||
pub fn walk_withitem<V: ASTVisitor + ?Sized>(visitor: &mut V, withitem: &Withitem) {
|
||||
visitor.visit_expr(&withitem.context_expr, None);
|
||||
if let Some(expr) = &withitem.optional_vars {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_match_case<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, match_case: &'a MatchCase) {
|
||||
pub fn walk_match_case<V: ASTVisitor + ?Sized>(visitor: &mut V, match_case: &MatchCase) {
|
||||
visitor.visit_pattern(&match_case.pattern);
|
||||
if let Some(expr) = &match_case.guard {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for stmt in &match_case.body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a Pattern) {
|
||||
pub fn walk_pattern<V: ASTVisitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
match &pattern.node {
|
||||
PatternKind::MatchValue { value } => visitor.visit_expr(value),
|
||||
PatternKind::MatchValue { value } => visitor.visit_expr(value, None),
|
||||
PatternKind::MatchSingleton { value } => visitor.visit_constant(value),
|
||||
PatternKind::MatchSequence { patterns } => {
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
visitor.visit_pattern(pattern)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchMapping { keys, patterns, .. } => {
|
||||
for expr in keys {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
@@ -547,7 +502,7 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
|
||||
kwd_patterns,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(cls);
|
||||
visitor.visit_expr(cls, None);
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
}
|
||||
@@ -559,7 +514,7 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
|
||||
PatternKind::MatchStar { .. } => {}
|
||||
PatternKind::MatchAs { pattern, .. } => {
|
||||
if let Some(pattern) = pattern {
|
||||
visitor.visit_pattern(pattern);
|
||||
visitor.visit_pattern(pattern)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchOr { patterns } => {
|
||||
@@ -572,28 +527,24 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
expr_context: &'a ExprContext,
|
||||
) {
|
||||
}
|
||||
pub fn walk_expr_context<V: ASTVisitor + ?Sized>(visitor: &mut V, expr_context: &ExprContext) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_boolop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, boolop: &'a Boolop) {}
|
||||
pub fn walk_boolop<V: ASTVisitor + ?Sized>(visitor: &mut V, boolop: &Boolop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_operator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, operator: &'a Operator) {}
|
||||
pub fn walk_operator<V: ASTVisitor + ?Sized>(visitor: &mut V, operator: &Operator) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_unaryop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, unaryop: &'a Unaryop) {}
|
||||
pub fn walk_unaryop<V: ASTVisitor + ?Sized>(visitor: &mut V, unaryop: &Unaryop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_cmpop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, cmpop: &'a Cmpop) {}
|
||||
pub fn walk_cmpop<V: ASTVisitor + ?Sized>(visitor: &mut V, cmpop: &Cmpop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, alias: &'a Alias) {}
|
||||
pub fn walk_alias<V: ASTVisitor + ?Sized>(visitor: &mut V, alias: &Alias) {}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod fixer;
|
||||
pub mod fixes;
|
||||
@@ -1,216 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, Fix};
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum Mode {
|
||||
Generate,
|
||||
Apply,
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Mode::Apply,
|
||||
false => Mode::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(checks: &mut [Check], contents: &str, path: &Path) -> Result<()> {
|
||||
if checks.iter().all(|check| check.fix.is_none()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let output = apply_fixes(
|
||||
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
|
||||
contents,
|
||||
);
|
||||
|
||||
fs::write(path, output).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
|
||||
let mut output = "".to_string();
|
||||
let mut last_pos: Location = Location::new(0, 0);
|
||||
|
||||
for fix in fixes {
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
|
||||
if last_pos > fix.start {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fix.start.row() > last_pos.row() {
|
||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..fix.start.row() - 1] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
output.push_str(&lines[fix.start.row() - 1][..fix.start.column() - 1]);
|
||||
output.push_str(&fix.content);
|
||||
} else {
|
||||
output.push_str(
|
||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.start.column() - 1],
|
||||
);
|
||||
output.push_str(&fix.content);
|
||||
}
|
||||
|
||||
last_pos = fix.end;
|
||||
fix.applied = true;
|
||||
}
|
||||
|
||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::autofix::fixer::apply_fixes;
|
||||
use crate::checks::Fix;
|
||||
|
||||
#[test]
|
||||
fn empty_file() -> Result<()> {
|
||||
let mut fixes = vec![];
|
||||
let actual = apply_fixes(fixes.iter_mut(), "");
|
||||
let expected = "";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_single_replacement() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
content: "Bar".to_string(),
|
||||
start: Location::new(1, 9),
|
||||
end: Location::new(1, 15),
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
|
||||
let expected = "class A(Bar):
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_single_removal() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 16),
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_double_removal() -> Result<()> {
|
||||
let mut fixes = vec![
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 17),
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 17),
|
||||
end: Location::new(1, 24),
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
"class A(object, object):
|
||||
...
|
||||
",
|
||||
);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_overlapping_fixes() -> Result<()> {
|
||||
let mut fixes = vec![
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 16),
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
content: "ignored".to_string(),
|
||||
start: Location::new(1, 10),
|
||||
end: Location::new(1, 12),
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
use rustpython_parser::ast::{Expr, Keyword, Location};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::token::Tok;
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::checks::Fix;
|
||||
|
||||
/// Convert a location within a file (relative to `base`) to an absolute position.
|
||||
fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
if relative.row() == 1 {
|
||||
Location::new(
|
||||
relative.row() + base.row() - 1,
|
||||
relative.column() + base.column() - 1,
|
||||
)
|
||||
} else {
|
||||
Location::new(relative.row() + base.row() - 1, relative.column())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a fix to remove a base from a ClassDef statement.
|
||||
pub fn remove_class_def_base(
|
||||
locator: &mut SourceCodeLocator,
|
||||
stmt_at: &Location,
|
||||
expr_at: Location,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Fix> {
|
||||
let content = locator.slice_source_code(stmt_at);
|
||||
|
||||
// Case 1: `object` is the only base.
|
||||
if bases.len() == 1 && keywords.is_empty() {
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(to_absolute(&start, stmt_at));
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
fix_end = Some(to_absolute(&end, stmt_at));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if bases
|
||||
.iter()
|
||||
.map(|node| node.location)
|
||||
.chain(keywords.iter().map(|node| node.location))
|
||||
.any(|location| location > expr_at)
|
||||
{
|
||||
// Case 2: `object` is _not_ the last node.
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
let mut seen_comma = false;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
let start = to_absolute(&start, stmt_at);
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::Newline) {
|
||||
fix_end = Some(end);
|
||||
} else {
|
||||
fix_end = Some(start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if start == expr_at {
|
||||
fix_start = Some(start);
|
||||
}
|
||||
if fix_start.is_some() && matches!(tok, Tok::Comma) {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
// Case 3: `object` is the last node, so we have to find the last token that isn't a comma.
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
let start = to_absolute(&start, stmt_at);
|
||||
let end = to_absolute(&end, stmt_at);
|
||||
if start == expr_at {
|
||||
fix_end = Some(end);
|
||||
break;
|
||||
}
|
||||
if matches!(tok, Tok::Comma) {
|
||||
fix_start = Some(start);
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +156,8 @@ pub const BUILTINS: &[&str] = &[
|
||||
// Globally defined names which are not attributes of the builtins module, or are only present on
|
||||
// some platforms.
|
||||
pub const MAGIC_GLOBALS: &[&str] = &[
|
||||
"WindowsError",
|
||||
"__annotations__",
|
||||
"__builtins__",
|
||||
"__file__",
|
||||
"__builtins__",
|
||||
"__annotations__",
|
||||
"WindowsError",
|
||||
];
|
||||
70
src/cache.rs
70
src/cache.rs
@@ -1,5 +1,4 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::Metadata;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -8,7 +7,6 @@ use filetime::FileTime;
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::message::Message;
|
||||
use crate::settings::Settings;
|
||||
|
||||
@@ -62,7 +60,7 @@ impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Mode::ReadWrite,
|
||||
false => Mode::None,
|
||||
false => Mode::WriteOnly,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,10 +69,9 @@ fn cache_dir() -> &'static str {
|
||||
"./.ruff_cache"
|
||||
}
|
||||
|
||||
fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String {
|
||||
fn cache_key(path: &Path, settings: &Settings) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
settings.hash(&mut hasher);
|
||||
autofix.hash(&mut hasher);
|
||||
format!(
|
||||
"{}@{}@{}",
|
||||
path.canonicalize().unwrap().to_string_lossy(),
|
||||
@@ -83,28 +80,22 @@ fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
path: &Path,
|
||||
metadata: &Metadata,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
mode: &Mode,
|
||||
) -> Option<Vec<Message>> {
|
||||
pub fn get(path: &Path, settings: &Settings, mode: &Mode) -> Option<Vec<Message>> {
|
||||
if !mode.allow_read() {
|
||||
return None;
|
||||
};
|
||||
|
||||
match cacache::read_sync(cache_dir(), cache_key(path, settings, autofix)) {
|
||||
Ok(encoded) => match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
metadata: CacheMetadata { mtime },
|
||||
messages,
|
||||
}) => {
|
||||
if FileTime::from_last_modification_time(metadata).unix_seconds() == mtime {
|
||||
return Some(messages);
|
||||
match cacache::read_sync(cache_dir(), cache_key(path, settings)) {
|
||||
Ok(encoded) => match path.metadata() {
|
||||
Ok(m) => match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult { metadata, messages }) => {
|
||||
if FileTime::from_last_modification_time(&m).unix_seconds() == metadata.mtime {
|
||||
return Some(messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
|
||||
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
|
||||
},
|
||||
Err(e) => error!("Failed to read metadata from path: {e:?}"),
|
||||
},
|
||||
Err(EntryNotFound(_, _)) => {}
|
||||
Err(e) => error!("Failed to read from cache: {e:?}"),
|
||||
@@ -112,29 +103,24 @@ pub fn get(
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
path: &Path,
|
||||
metadata: &Metadata,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
messages: &[Message],
|
||||
mode: &Mode,
|
||||
) {
|
||||
pub fn set(path: &Path, settings: &Settings, messages: &[Message], mode: &Mode) {
|
||||
if !mode.allow_write() {
|
||||
return;
|
||||
};
|
||||
|
||||
let check_result = CheckResultRef {
|
||||
metadata: &CacheMetadata {
|
||||
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
|
||||
},
|
||||
messages,
|
||||
};
|
||||
if let Err(e) = cacache::write_sync(
|
||||
cache_dir(),
|
||||
cache_key(path, settings, autofix),
|
||||
bincode::serialize(&check_result).unwrap(),
|
||||
) {
|
||||
error!("Failed to write to cache: {e:?}")
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
let check_result = CheckResultRef {
|
||||
metadata: &CacheMetadata {
|
||||
mtime: FileTime::from_last_modification_time(&metadata).unix_seconds(),
|
||||
},
|
||||
messages,
|
||||
};
|
||||
if let Err(e) = cacache::write_sync(
|
||||
cache_dir(),
|
||||
cache_key(path, settings),
|
||||
bincode::serialize(&check_result).unwrap(),
|
||||
) {
|
||||
error!("Failed to write to cache: {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1200
src/check_ast.rs
1200
src/check_ast.rs
File diff suppressed because it is too large
Load Diff
164
src/check_cst.rs
Normal file
164
src/check_cst.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bat::PrettyPrinter;
|
||||
use bumpalo::Bump;
|
||||
use libcst_native::{
|
||||
Arg, ClassDef, Codegen, Expression, FormattedStringContent, If, Module, SimpleString,
|
||||
};
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::cst_visitor;
|
||||
use crate::cst_visitor::CSTVisitor;
|
||||
use crate::settings::Settings;
|
||||
|
||||
enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
struct Scope {
|
||||
kind: ScopeKind,
|
||||
values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
FutureImportation,
|
||||
Importation,
|
||||
StarImportation,
|
||||
SubmoduleImportation,
|
||||
}
|
||||
|
||||
struct Binding {
|
||||
kind: BindingKind,
|
||||
name: String,
|
||||
location: Location,
|
||||
used: bool,
|
||||
}
|
||||
|
||||
struct Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
checks: Vec<Check>,
|
||||
arena: Vec<String>,
|
||||
}
|
||||
|
||||
impl Checker<'_> {
|
||||
pub fn new(settings: &Settings) -> Checker {
|
||||
Checker {
|
||||
settings,
|
||||
checks: vec![],
|
||||
arena: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QUOTE: &str = "\"";
|
||||
|
||||
impl<'b> CSTVisitor for Checker<'_> {
|
||||
fn visit_Expression<'a>(&mut self, node: &'a Expression<'a>) -> Expression<'a> {
|
||||
match node {
|
||||
Expression::FormattedString(node) => match &node.parts[..] {
|
||||
[node] => match node {
|
||||
FormattedStringContent::Text(node) => {
|
||||
self.arena.push(format!("\"{}\"", node.value));
|
||||
return Expression::SimpleString(Box::new(SimpleString {
|
||||
value: node.value,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
cst_visitor::walk_Expression(self, node)
|
||||
}
|
||||
|
||||
fn visit_ClassDef<'a>(&mut self, node: &'a ClassDef<'a>) -> ClassDef<'a> {
|
||||
let mut bases: Vec<Arg<'a>> = node
|
||||
.bases
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|node| {
|
||||
if let Expression::Name(node) = &node.value {
|
||||
node.value != "object"
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut transformed: ClassDef<'a> = node.clone();
|
||||
if bases.is_empty() {
|
||||
transformed.lpar = None;
|
||||
transformed.rpar = None;
|
||||
} else {
|
||||
let node = bases.last_mut().unwrap();
|
||||
node.comma = None;
|
||||
}
|
||||
transformed.bases = bases;
|
||||
transformed
|
||||
}
|
||||
|
||||
fn visit_If(&mut self, node: &If) {
|
||||
if let Expression::Tuple { .. } = node.test {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::IfTuple,
|
||||
location: Default::default(),
|
||||
});
|
||||
}
|
||||
cst_visitor::walk_If(self, node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_cst<'a>(python_cst: &'a Module<'a>, settings: &Settings) -> Vec<Check> {
|
||||
// // Create a new arena to bump allocate into.
|
||||
// let bump = Bump::new();
|
||||
//
|
||||
// // Allocate values into the arena.
|
||||
// let scooter = bump.alloc(python_cst.clone());
|
||||
|
||||
let mut x = python_cst.clone();
|
||||
let mut s = Default::default();
|
||||
x.codegen(&mut s);
|
||||
|
||||
println!("Starting from source:");
|
||||
println!("```");
|
||||
let source = s.to_string().into_bytes();
|
||||
PrettyPrinter::new()
|
||||
.input_from_bytes(&source)
|
||||
.language("python")
|
||||
.print()
|
||||
.unwrap();
|
||||
println!("```");
|
||||
|
||||
let mut checker = Checker::new(settings);
|
||||
let mut transformed = checker.visit_Module(python_cst);
|
||||
|
||||
let mut state = Default::default();
|
||||
transformed.codegen(&mut state);
|
||||
|
||||
println!("");
|
||||
println!("Generated output:");
|
||||
println!("```");
|
||||
let source = state.to_string().into_bytes();
|
||||
PrettyPrinter::new()
|
||||
.input_from_bytes(&source)
|
||||
.language("python")
|
||||
.print()
|
||||
.unwrap();
|
||||
println!("```");
|
||||
|
||||
checker.checks
|
||||
}
|
||||
@@ -1,26 +1,10 @@
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::settings::Settings;
|
||||
|
||||
/// Whether the given line is too long and should be reported.
|
||||
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
if length > limit {
|
||||
let mut chunks = line.split_whitespace();
|
||||
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
|
||||
// Do not enforce the line length for commented lines with a single word
|
||||
!(first == "#" && chunks.next().is_none())
|
||||
} else {
|
||||
// Single word / no printable chars - no way to make the line shorter
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings) {
|
||||
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
|
||||
let enforce_line_too_ling = settings.select.contains(CheckKind::LineTooLong.code());
|
||||
|
||||
let mut line_checks = vec![];
|
||||
let mut ignored = vec![];
|
||||
@@ -34,13 +18,13 @@ pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings)
|
||||
}
|
||||
|
||||
// Enforce line length.
|
||||
if enforce_line_too_long {
|
||||
let line_length = line.len();
|
||||
if should_enforce_line_length(line, line_length, settings.line_length) {
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Location::new(row + 1, settings.line_length + 1),
|
||||
);
|
||||
if enforce_line_too_ling && line.len() > settings.line_length {
|
||||
let chunks: Vec<&str> = line.split_whitespace().collect();
|
||||
if !(chunks.len() == 1 || (chunks.len() == 2 && chunks[0] == "#")) {
|
||||
let check = Check {
|
||||
kind: CheckKind::LineTooLong,
|
||||
location: Location::new(row + 1, settings.line_length + 1),
|
||||
};
|
||||
if !check.is_inline_ignored(line) {
|
||||
line_checks.push(check);
|
||||
}
|
||||
|
||||
334
src/checks.rs
334
src/checks.rs
@@ -8,44 +8,18 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
|
||||
pub enum CheckCode {
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
E712,
|
||||
E713,
|
||||
E714,
|
||||
E722,
|
||||
E731,
|
||||
E741,
|
||||
E742,
|
||||
E743,
|
||||
E902,
|
||||
F401,
|
||||
F403,
|
||||
F404,
|
||||
F406,
|
||||
F407,
|
||||
F541,
|
||||
F601,
|
||||
F602,
|
||||
F621,
|
||||
F622,
|
||||
F631,
|
||||
F634,
|
||||
F701,
|
||||
F702,
|
||||
F704,
|
||||
F706,
|
||||
F707,
|
||||
F722,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F841,
|
||||
F901,
|
||||
R001,
|
||||
R002,
|
||||
}
|
||||
|
||||
impl FromStr for CheckCode {
|
||||
@@ -53,43 +27,18 @@ impl FromStr for CheckCode {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"E402" => Ok(CheckCode::E402),
|
||||
"E501" => Ok(CheckCode::E501),
|
||||
"E711" => Ok(CheckCode::E711),
|
||||
"E712" => Ok(CheckCode::E712),
|
||||
"E713" => Ok(CheckCode::E713),
|
||||
"E722" => Ok(CheckCode::E722),
|
||||
"E714" => Ok(CheckCode::E714),
|
||||
"E731" => Ok(CheckCode::E731),
|
||||
"E741" => Ok(CheckCode::E741),
|
||||
"E742" => Ok(CheckCode::E742),
|
||||
"E743" => Ok(CheckCode::E743),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F404" => Ok(CheckCode::F404),
|
||||
"F406" => Ok(CheckCode::F406),
|
||||
"F407" => Ok(CheckCode::F407),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
"F601" => Ok(CheckCode::F601),
|
||||
"F602" => Ok(CheckCode::F602),
|
||||
"F621" => Ok(CheckCode::F621),
|
||||
"F622" => Ok(CheckCode::F622),
|
||||
"F631" => Ok(CheckCode::F631),
|
||||
"F634" => Ok(CheckCode::F634),
|
||||
"F701" => Ok(CheckCode::F701),
|
||||
"F702" => Ok(CheckCode::F702),
|
||||
"F704" => Ok(CheckCode::F704),
|
||||
"F706" => Ok(CheckCode::F706),
|
||||
"F707" => Ok(CheckCode::F707),
|
||||
"F821" => Ok(CheckCode::F821),
|
||||
"F822" => Ok(CheckCode::F822),
|
||||
"F823" => Ok(CheckCode::F823),
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
"R001" => Ok(CheckCode::R001),
|
||||
"R002" => Ok(CheckCode::R002),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
}
|
||||
@@ -98,53 +47,36 @@ impl FromStr for CheckCode {
|
||||
impl CheckCode {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
CheckCode::E402 => "E402",
|
||||
CheckCode::E501 => "E501",
|
||||
CheckCode::E711 => "E711",
|
||||
CheckCode::E712 => "E712",
|
||||
CheckCode::E713 => "E713",
|
||||
CheckCode::E714 => "E714",
|
||||
CheckCode::E722 => "E722",
|
||||
CheckCode::E731 => "E731",
|
||||
CheckCode::E741 => "E741",
|
||||
CheckCode::E742 => "E742",
|
||||
CheckCode::E743 => "E743",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F404 => "F404",
|
||||
CheckCode::F406 => "F406",
|
||||
CheckCode::F407 => "F407",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F601 => "F601",
|
||||
CheckCode::F602 => "F602",
|
||||
CheckCode::F621 => "F621",
|
||||
CheckCode::F622 => "F622",
|
||||
CheckCode::F631 => "F631",
|
||||
CheckCode::F634 => "F634",
|
||||
CheckCode::F701 => "F701",
|
||||
CheckCode::F702 => "F702",
|
||||
CheckCode::F704 => "F704",
|
||||
CheckCode::F706 => "F706",
|
||||
CheckCode::F707 => "F707",
|
||||
CheckCode::F722 => "F722",
|
||||
CheckCode::F821 => "F821",
|
||||
CheckCode::F822 => "F822",
|
||||
CheckCode::F823 => "F823",
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
}
|
||||
}
|
||||
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
/// The source for the check (either the AST, or the physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 => &LintSource::Lines,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
CheckCode::F401 => &LintSource::AST,
|
||||
CheckCode::F403 => &LintSource::AST,
|
||||
CheckCode::F541 => &LintSource::AST,
|
||||
CheckCode::F634 => &LintSource::AST,
|
||||
CheckCode::F704 => &LintSource::AST,
|
||||
CheckCode::F706 => &LintSource::AST,
|
||||
CheckCode::F821 => &LintSource::AST,
|
||||
CheckCode::F823 => &LintSource::AST,
|
||||
CheckCode::F831 => &LintSource::AST,
|
||||
CheckCode::F841 => &LintSource::AST,
|
||||
CheckCode::F901 => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,144 +85,39 @@ impl CheckCode {
|
||||
pub enum LintSource {
|
||||
AST,
|
||||
Lines,
|
||||
FileSystem,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RejectedCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
AssertTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
DefaultExceptNotLast,
|
||||
DoNotAssignLambda,
|
||||
DoNotUseBareExcept,
|
||||
DuplicateArgumentName,
|
||||
ForwardAnnotationSyntaxError(String),
|
||||
FStringMissingPlaceholders,
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportStarNotPermitted(String),
|
||||
ImportStarUsage(String),
|
||||
LateFutureImport,
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
NoAssertEquals,
|
||||
NoneComparison(RejectedCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
ImportStarUsage,
|
||||
LineTooLong,
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
TooManyExpressionsInStarredAssignment,
|
||||
TrueFalseComparison(bool, RejectedCmpop),
|
||||
TwoStarredExpressions,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(String),
|
||||
UnusedVariable(String),
|
||||
UselessObjectInheritance(String),
|
||||
YieldOutsideFunction,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
/// The name of the check.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
|
||||
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
|
||||
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
|
||||
CheckKind::AssertTuple => "AssertTuple",
|
||||
CheckKind::BreakOutsideLoop => "BreakOutsideLoop",
|
||||
CheckKind::ContinueOutsideLoop => "ContinueOutsideLoop",
|
||||
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
|
||||
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
|
||||
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
|
||||
CheckKind::ImportStarUsage(_) => "ImportStarUsage",
|
||||
CheckKind::LateFutureImport => "LateFutureImport",
|
||||
CheckKind::LineTooLong(_, _) => "LineTooLong",
|
||||
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
|
||||
CheckKind::NoAssertEquals => "NoAssertEquals",
|
||||
CheckKind::NoneComparison(_) => "NoneComparison",
|
||||
CheckKind::NotInTest => "NotInTest",
|
||||
CheckKind::NotIsTest => "NotIsTest",
|
||||
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
|
||||
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
|
||||
CheckKind::TooManyExpressionsInStarredAssignment => {
|
||||
"TooManyExpressionsInStarredAssignment"
|
||||
}
|
||||
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
|
||||
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
|
||||
CheckKind::UndefinedExport(_) => "UndefinedExport",
|
||||
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
|
||||
CheckKind::UndefinedName(_) => "UndefinedName",
|
||||
CheckKind::UnusedImport(_) => "UnusedImport",
|
||||
CheckKind::UnusedVariable(_) => "UnusedVariable",
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
}
|
||||
}
|
||||
|
||||
/// A four-letter shorthand code for the check.
|
||||
pub fn code(&self) -> &'static CheckCode {
|
||||
match self {
|
||||
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
|
||||
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
|
||||
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
|
||||
CheckKind::AssertTuple => &CheckCode::F631,
|
||||
CheckKind::BreakOutsideLoop => &CheckCode::F701,
|
||||
CheckKind::ContinueOutsideLoop => &CheckCode::F702,
|
||||
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
|
||||
CheckKind::DoNotAssignLambda => &CheckCode::E731,
|
||||
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
|
||||
CheckKind::DuplicateArgumentName => &CheckCode::F831,
|
||||
CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722,
|
||||
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
|
||||
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
|
||||
CheckKind::ImportStarUsage(_) => &CheckCode::F403,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong(_, _) => &CheckCode::E501,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
|
||||
CheckKind::NoAssertEquals => &CheckCode::R002,
|
||||
CheckKind::NoneComparison(_) => &CheckCode::E711,
|
||||
CheckKind::NotInTest => &CheckCode::E713,
|
||||
CheckKind::NotIsTest => &CheckCode::E714,
|
||||
CheckKind::ImportStarUsage => &CheckCode::F403,
|
||||
CheckKind::LineTooLong => &CheckCode::E501,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621,
|
||||
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
|
||||
CheckKind::TwoStarredExpressions => &CheckCode::F622,
|
||||
CheckKind::UndefinedExport(_) => &CheckCode::F822,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
}
|
||||
}
|
||||
@@ -298,147 +125,42 @@ impl CheckKind {
|
||||
/// The body text for the check.
|
||||
pub fn body(&self) -> String {
|
||||
match self {
|
||||
CheckKind::AmbiguousClassName(name) => {
|
||||
format!("ambiguous class name '{}'", name)
|
||||
}
|
||||
CheckKind::AmbiguousFunctionName(name) => {
|
||||
format!("ambiguous function name '{}'", name)
|
||||
}
|
||||
CheckKind::AmbiguousVariableName(name) => {
|
||||
format!("ambiguous variable name '{}'", name)
|
||||
}
|
||||
CheckKind::AssertTuple => {
|
||||
"Assert test is a non-empty tuple, which is always `True`".to_string()
|
||||
}
|
||||
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
|
||||
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
|
||||
CheckKind::DefaultExceptNotLast => {
|
||||
"an `except:` block as not the last exception handler".to_string()
|
||||
}
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
}
|
||||
CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(),
|
||||
CheckKind::DuplicateArgumentName => {
|
||||
"Duplicate argument name in function definition".to_string()
|
||||
}
|
||||
CheckKind::ForwardAnnotationSyntaxError(body) => {
|
||||
format!("syntax error in forward annotation '{body}'")
|
||||
}
|
||||
CheckKind::FStringMissingPlaceholders => {
|
||||
"f-string without any placeholders".to_string()
|
||||
}
|
||||
CheckKind::FutureFeatureNotDefined(name) => {
|
||||
format!("future feature '{name}' is not defined")
|
||||
}
|
||||
CheckKind::IOError(name) => {
|
||||
format!("No such file or directory: `{name}`")
|
||||
}
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::ImportStarNotPermitted(name) => {
|
||||
format!("`from {name} import *` only allowed at module level")
|
||||
}
|
||||
CheckKind::ImportStarUsage(name) => {
|
||||
format!("`from {name} import *` used; unable to detect undefined names")
|
||||
}
|
||||
CheckKind::LateFutureImport => {
|
||||
"from __future__ imports must occur at the beginning of the file".to_string()
|
||||
}
|
||||
CheckKind::LineTooLong(length, limit) => {
|
||||
format!("Line too long ({length} > {limit} characters)")
|
||||
}
|
||||
CheckKind::ModuleImportNotAtTopOfFile => {
|
||||
"Module level import not at top of file".to_string()
|
||||
}
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => {
|
||||
"Dictionary key literal repeated".to_string()
|
||||
}
|
||||
CheckKind::MultiValueRepeatedKeyVariable(name) => {
|
||||
format!("Dictionary key `{name}` repeated")
|
||||
}
|
||||
CheckKind::NoAssertEquals => {
|
||||
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
|
||||
}
|
||||
CheckKind::NoneComparison(op) => match op {
|
||||
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `None` should be `cond is not None`".to_string()
|
||||
}
|
||||
},
|
||||
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
|
||||
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
|
||||
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
|
||||
CheckKind::LineTooLong => "Line too long".to_string(),
|
||||
CheckKind::RaiseNotImplemented => {
|
||||
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
|
||||
"'raise NotImplemented' should be 'raise NotImplementedError".to_string()
|
||||
}
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"a `return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::TooManyExpressionsInStarredAssignment => {
|
||||
"too many expressions in star-unpacking assignment".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(value, op) => match *value {
|
||||
true => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `True` should be `cond is True`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `True` should be `cond is not True`".to_string()
|
||||
}
|
||||
},
|
||||
false => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `False` should be `cond is False`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `False` should be `cond is not False`".to_string()
|
||||
}
|
||||
},
|
||||
},
|
||||
CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(),
|
||||
CheckKind::UndefinedExport(name) => {
|
||||
format!("Undefined name `{name}` in `__all__`")
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UndefinedLocal(name) => {
|
||||
format!("Local variable `{name}` referenced before assignment")
|
||||
}
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
|
||||
CheckKind::UnusedVariable(name) => {
|
||||
format!("Local variable `{name}` is assigned to but never used")
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
}
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"a `yield` or `yield from` statement outside of a function/method".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the check kind is (potentially) fixable.
|
||||
pub fn fixable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Fix {
|
||||
pub content: String,
|
||||
pub start: Location,
|
||||
pub end: Location,
|
||||
pub applied: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Check {
|
||||
pub kind: CheckKind,
|
||||
pub location: Location,
|
||||
pub fix: Option<Fix>,
|
||||
}
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
@@ -447,18 +169,6 @@ static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
impl Check {
|
||||
pub fn new(kind: CheckKind, location: Location) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
location,
|
||||
fix: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amend(&mut self, fix: Fix) {
|
||||
self.fix = Some(fix);
|
||||
}
|
||||
|
||||
pub fn is_inline_ignored(&self, line: &str) -> bool {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("codes") {
|
||||
|
||||
1039
src/cst_visitor.rs
Normal file
1039
src/cst_visitor.rs
Normal file
File diff suppressed because it is too large
Load Diff
10
src/lib.rs
10
src/lib.rs
@@ -1,15 +1,15 @@
|
||||
extern crate core;
|
||||
|
||||
mod ast;
|
||||
mod autofix;
|
||||
mod ast_ops;
|
||||
mod ast_visitor;
|
||||
mod builtins;
|
||||
mod cache;
|
||||
pub mod check_ast;
|
||||
mod check_cst;
|
||||
mod check_lines;
|
||||
pub mod checks;
|
||||
mod cst_visitor;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod pyproject;
|
||||
mod python;
|
||||
pub mod settings;
|
||||
|
||||
1449
src/linter.rs
1449
src/linter.rs
File diff suppressed because it is too large
Load Diff
113
src/main.rs
113
src/main.rs
@@ -6,26 +6,21 @@ use std::time::Instant;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use colored::Colorize;
|
||||
use glob::Pattern;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use ::ruff::checks::CheckCode;
|
||||
use ::ruff::checks::CheckKind;
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::lint_path;
|
||||
use ::ruff::linter::check_path;
|
||||
use ::ruff::logging::set_up_logging;
|
||||
use ::ruff::message::Message;
|
||||
use ::ruff::settings::Settings;
|
||||
use ::ruff::tell_user;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(name = format!("{CARGO_PKG_NAME} (v{CARGO_PKG_VERSION})"))]
|
||||
#[clap(name = "ruff")]
|
||||
#[clap(about = "An extremely fast Python linter.", long_about = None)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::AnyPath, required = true)]
|
||||
@@ -42,56 +37,21 @@ struct Cli {
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[clap(short, long, action)]
|
||||
watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[clap(short, long, action)]
|
||||
fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[clap(short, long, action)]
|
||||
no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
/// Comma-separated list of error codes to enable.
|
||||
#[clap(long, multiple = true)]
|
||||
select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
/// Comma-separated list of error codes to ignore.
|
||||
#[clap(long, multiple = true)]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// List of file and/or directory patterns to exclude from checks.
|
||||
#[clap(long, multiple = true)]
|
||||
exclude: Vec<Pattern>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
|
||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
||||
|
||||
if let Some(new_version) = informer.check_version().ok().flatten() {
|
||||
let msg = format!(
|
||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
||||
pkg_version = CARGO_PKG_VERSION,
|
||||
new_version = new_version.to_string().green()
|
||||
);
|
||||
|
||||
let cmd = format!(
|
||||
"Run to update: {cmd} {pkg_name}",
|
||||
cmd = "pip3 install --upgrade".green(),
|
||||
pkg_name = CARGO_PKG_NAME.green()
|
||||
);
|
||||
|
||||
println!("\n{msg}\n{cmd}");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
cache: bool,
|
||||
autofix: bool,
|
||||
) -> Result<Vec<Message>> {
|
||||
fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result<Vec<Message>> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<DirEntry> = files
|
||||
let files: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude))
|
||||
.collect();
|
||||
@@ -99,30 +59,16 @@ fn run_once(
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut messages: Vec<Message> = paths
|
||||
let mut messages: Vec<Message> = files
|
||||
.par_iter()
|
||||
.map(|entry| {
|
||||
lint_path(entry.path(), settings, &cache.into(), &autofix.into()).unwrap_or_else(|e| {
|
||||
check_path(entry.path(), settings, &cache.into()).unwrap_or_else(|e| {
|
||||
error!("Failed to check {}: {e:?}", entry.path().to_string_lossy());
|
||||
vec![]
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if settings.select.contains(&CheckCode::E902) {
|
||||
for file in files {
|
||||
if !file.exists() {
|
||||
messages.push(Message {
|
||||
kind: CheckKind::IOError(file.to_string_lossy().to_string()),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
filename: file.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort_unstable();
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked files in: {:?}", duration);
|
||||
@@ -131,32 +77,13 @@ fn run_once(
|
||||
}
|
||||
|
||||
fn report_once(messages: &[Message]) -> Result<()> {
|
||||
let (fixed, outstanding): (Vec<&Message>, Vec<&Message>) =
|
||||
messages.iter().partition(|message| message.fixed);
|
||||
let num_fixable = outstanding
|
||||
.iter()
|
||||
.filter(|message| message.kind.fixable())
|
||||
.count();
|
||||
// println!("Found {} error(s).", messages.len());
|
||||
|
||||
if !outstanding.is_empty() {
|
||||
for message in &outstanding {
|
||||
if !messages.is_empty() {
|
||||
println!();
|
||||
for message in messages {
|
||||
println!("{}", message);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if !fixed.is_empty() {
|
||||
println!(
|
||||
"Found {} error(s) ({} fixed).",
|
||||
outstanding.len(),
|
||||
fixed.len()
|
||||
);
|
||||
} else {
|
||||
println!("Found {} error(s).", outstanding.len());
|
||||
}
|
||||
|
||||
if num_fixable > 0 {
|
||||
println!("{num_fixable} potentially fixable with the --fix option.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -192,20 +119,13 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore(&cli.ignore);
|
||||
}
|
||||
if !cli.exclude.is_empty() {
|
||||
settings.exclude(cli.exclude);
|
||||
}
|
||||
|
||||
if cli.watch {
|
||||
if cli.fix {
|
||||
println!("Warning: --fix is not enabled in watch mode.")
|
||||
}
|
||||
|
||||
// Perform an initial run instantly.
|
||||
clearscreen::clear()?;
|
||||
tell_user!("Starting linter in watch mode...\n");
|
||||
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache)?;
|
||||
if !cli.quiet {
|
||||
report_continuously(&messages)?;
|
||||
}
|
||||
@@ -225,7 +145,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
clearscreen::clear()?;
|
||||
tell_user!("File change detected...\n");
|
||||
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache)?;
|
||||
if !cli.quiet {
|
||||
report_continuously(&messages)?;
|
||||
}
|
||||
@@ -236,14 +156,11 @@ fn inner_main() -> Result<ExitCode> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache)?;
|
||||
if !cli.quiet {
|
||||
report_once(&messages)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
check_for_updates();
|
||||
|
||||
if !messages.is_empty() && !cli.exit_zero {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,25 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::CheckKind;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "Location")]
|
||||
struct LocationDef {
|
||||
#[serde(getter = "Location::row")]
|
||||
row: usize,
|
||||
#[serde(getter = "Location::column")]
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl From<LocationDef> for Location {
|
||||
fn from(def: LocationDef) -> Location {
|
||||
Location::new(def.row, def.column)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub kind: CheckKind,
|
||||
pub fixed: bool,
|
||||
#[serde(with = "LocationDef")]
|
||||
pub location: Location,
|
||||
pub filename: String,
|
||||
}
|
||||
@@ -20,7 +35,7 @@ impl Ord for Message {
|
||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
||||
&other.filename,
|
||||
other.location.row(),
|
||||
other.location.column(),
|
||||
self.location.column(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -39,8 +40,7 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(Pat
|
||||
pub struct Config {
|
||||
pub line_length: Option<usize>,
|
||||
pub exclude: Option<Vec<PathBuf>>,
|
||||
pub select: Option<Vec<CheckCode>>,
|
||||
pub ignore: Option<Vec<CheckCode>>,
|
||||
pub select: Option<BTreeSet<CheckCode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
@@ -90,6 +90,7 @@ fn find_project_root<'a>(sources: impl IntoIterator<Item = &'a Path>) -> Option<
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -124,7 +125,6 @@ mod tests {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
select: None,
|
||||
ignore: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -143,7 +143,6 @@ line-length = 79
|
||||
line_length: Some(79),
|
||||
exclude: None,
|
||||
select: None,
|
||||
ignore: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -162,7 +161,6 @@ exclude = ["foo.py"]
|
||||
line_length: None,
|
||||
exclude: Some(vec![Path::new("foo.py").to_path_buf()]),
|
||||
select: None,
|
||||
ignore: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -180,27 +178,7 @@ select = ["E501"]
|
||||
ruff: Some(Config {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
select: Some(vec![CheckCode::E501]),
|
||||
ignore: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
let pyproject: PyProject = toml::from_str(
|
||||
r#"
|
||||
[tool.black]
|
||||
[tool.ruff]
|
||||
ignore = ["E501"]
|
||||
"#,
|
||||
)?;
|
||||
assert_eq!(
|
||||
pyproject.tool,
|
||||
Some(Tools {
|
||||
ruff: Some(Config {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
select: None,
|
||||
ignore: Some(vec![CheckCode::E501]),
|
||||
select: Some(BTreeSet::from([CheckCode::E501])),
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -238,17 +216,18 @@ other-attribute = 1
|
||||
|
||||
#[test]
|
||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
||||
let project_root = find_project_root([Path::new("resources/test/fixtures/__init__.py")])
|
||||
let project_root = find_project_root([Path::new("resources/test/src/__init__.py")])
|
||||
.expect("Unable to find project root.");
|
||||
assert_eq!(project_root, Path::new("resources/test/fixtures"));
|
||||
assert_eq!(project_root, Path::new("resources/test/src"));
|
||||
|
||||
let path = find_pyproject_toml(&project_root).expect("Unable to find pyproject.toml.");
|
||||
assert_eq!(path, Path::new("resources/test/fixtures/pyproject.toml"));
|
||||
assert_eq!(path, Path::new("resources/test/src/pyproject.toml"));
|
||||
|
||||
let pyproject = parse_pyproject_toml(&path)?;
|
||||
let config = pyproject
|
||||
.tool
|
||||
.and_then(|tool| tool.ruff)
|
||||
.map(|tool| tool.ruff)
|
||||
.flatten()
|
||||
.expect("Unable to find tool.ruff.");
|
||||
assert_eq!(
|
||||
config,
|
||||
@@ -258,47 +237,20 @@ other-attribute = 1
|
||||
Path::new("excluded.py").to_path_buf(),
|
||||
Path::new("**/migrations").to_path_buf()
|
||||
]),
|
||||
select: Some(vec![
|
||||
CheckCode::E402,
|
||||
select: Some(BTreeSet::from([
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
]),
|
||||
ignore: None,
|
||||
])),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod builtins;
|
||||
pub mod future;
|
||||
pub mod typing;
|
||||
@@ -1,13 +0,0 @@
|
||||
/// A copy of `__future__.all_feature_names`.
|
||||
pub const ALL_FEATURE_NAMES: &[&str] = &[
|
||||
"nested_scopes",
|
||||
"generators",
|
||||
"division",
|
||||
"absolute_import",
|
||||
"with_statement",
|
||||
"print_function",
|
||||
"unicode_literals",
|
||||
"barry_as_FLUFL",
|
||||
"generator_stop",
|
||||
"annotations",
|
||||
];
|
||||
@@ -1,88 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
lazy_static! {
|
||||
static ref ANNOTATED_SUBSCRIPTS: BTreeSet<&'static str> = BTreeSet::from([
|
||||
"AbstractAsyncContextManager",
|
||||
"AbstractContextManager",
|
||||
"AbstractSet",
|
||||
"AsyncContextManager",
|
||||
"AsyncGenerator",
|
||||
"AsyncIterable",
|
||||
"AsyncIterator",
|
||||
"Awaitable",
|
||||
"BinaryIO",
|
||||
"BsdDbShelf",
|
||||
"ByteString",
|
||||
"Callable",
|
||||
"ChainMap",
|
||||
"ClassVar",
|
||||
"Collection",
|
||||
"Concatenate",
|
||||
"Container",
|
||||
"ContextManager",
|
||||
"Coroutine",
|
||||
"Counter",
|
||||
"Counter",
|
||||
"DbfilenameShelf",
|
||||
"DefaultDict",
|
||||
"Deque",
|
||||
"Dict",
|
||||
"Field",
|
||||
"Final",
|
||||
"FrozenSet",
|
||||
"Generator",
|
||||
"Iterator",
|
||||
"Generic",
|
||||
"IO",
|
||||
"ItemsView",
|
||||
"Iterable",
|
||||
"Iterator",
|
||||
"KeysView",
|
||||
"LifoQueue",
|
||||
"List",
|
||||
"Mapping",
|
||||
"MappingProxyType",
|
||||
"MappingView",
|
||||
"Match",
|
||||
"MutableMapping",
|
||||
"MutableSequence",
|
||||
"MutableSet",
|
||||
"Optional",
|
||||
"OrderedDict",
|
||||
"PathLike",
|
||||
"Pattern",
|
||||
"PriorityQueue",
|
||||
"Protocol",
|
||||
"Queue",
|
||||
"Reversible",
|
||||
"Sequence",
|
||||
"Set",
|
||||
"Shelf",
|
||||
"SimpleQueue",
|
||||
"TextIO",
|
||||
"Tuple",
|
||||
"Type",
|
||||
"TypeGuard",
|
||||
"Union",
|
||||
"ValuesView",
|
||||
"WeakKeyDictionary",
|
||||
"WeakMethod",
|
||||
"WeakSet",
|
||||
"WeakValueDictionary",
|
||||
"cached_property",
|
||||
"defaultdict",
|
||||
"deque",
|
||||
"dict",
|
||||
"frozenset",
|
||||
"list",
|
||||
"partialmethod",
|
||||
"set",
|
||||
"tuple",
|
||||
"type",
|
||||
]);
|
||||
}
|
||||
|
||||
pub fn is_annotated_subscript(name: &str) -> bool {
|
||||
ANNOTATED_SUBSCRIPTS.contains(name)
|
||||
}
|
||||
@@ -27,7 +27,7 @@ impl Hash for Settings {
|
||||
impl Settings {
|
||||
pub fn from_paths<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<Self> {
|
||||
let (project_root, config) = load_config(paths)?;
|
||||
let mut settings = Settings {
|
||||
Ok(Settings {
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
exclude: config
|
||||
.exclude
|
||||
@@ -42,53 +42,20 @@ impl Settings {
|
||||
})
|
||||
.map(|path| Pattern::new(&path.to_string_lossy()).expect("Invalid pattern."))
|
||||
.collect(),
|
||||
select: BTreeSet::from_iter(config.select.unwrap_or_else(|| {
|
||||
vec![
|
||||
CheckCode::E402,
|
||||
select: config.select.unwrap_or_else(|| {
|
||||
BTreeSet::from([
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F823,
|
||||
CheckCode::F901,
|
||||
// Disable refactoring codes by default.
|
||||
// CheckCode::R001,
|
||||
// CheckCode::R002,
|
||||
]
|
||||
})),
|
||||
};
|
||||
if let Some(ignore) = &config.ignore {
|
||||
settings.ignore(ignore);
|
||||
}
|
||||
Ok(settings)
|
||||
])
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select(&mut self, codes: Vec<CheckCode>) {
|
||||
@@ -103,8 +70,4 @@ impl Settings {
|
||||
self.select.remove(code);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, exclude: Vec<Pattern>) {
|
||||
self.exclude = exclude;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user