Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e19142d0e | ||
|
|
6a95dade6d | ||
|
|
d6e765877e | ||
|
|
e4d36bae57 | ||
|
|
e3531276a7 | ||
|
|
634553f188 | ||
|
|
4ff0b75045 | ||
|
|
481d668511 | ||
|
|
a9f56ee76e | ||
|
|
b4bfa87104 | ||
|
|
b9f42bf5e5 | ||
|
|
8281d414ca | ||
|
|
7e45a9f2e2 | ||
|
|
a000cd4a09 | ||
|
|
d8b4b92733 | ||
|
|
27de342e75 | ||
|
|
d805067683 | ||
|
|
1ea2e93f8e | ||
|
|
dc180dc277 | ||
|
|
6be910ae07 | ||
|
|
ba85eb846c | ||
|
|
d067efe265 | ||
|
|
549ea2f85f | ||
|
|
d814ebd21f | ||
|
|
3f272b6cf8 | ||
|
|
76891a8c07 | ||
|
|
e389201b5f | ||
|
|
4b2020d03a | ||
|
|
0aa356c96c | ||
|
|
630b4b627d | ||
|
|
854cd14842 | ||
|
|
6b93c8403f | ||
|
|
765d21c7b0 | ||
|
|
a58b9b5063 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -115,7 +115,9 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: pip install black[d]==22.12.0
|
||||
- run: cargo test --all
|
||||
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
|
||||
|
||||
maturin_build:
|
||||
name: "maturin build"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.178
|
||||
rev: v0.0.184
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.181
|
||||
|
||||
### Files excluded by `.gitignore` are now ignored ([#1234](https://github.com/charliermarsh/ruff/pull/1234))
|
||||
|
||||
Ruff will now avoid checking files that are excluded by `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files. This behavior is powered by the [`ignore`](https://docs.rs/ignore/latest/ignore/struct.WalkBuilder.html#ignore-rules)
|
||||
crate, and is applied in addition to Ruff's built-in `exclude` system.
|
||||
|
||||
To disable this behavior, set `respect-gitignore = false` in your `pyproject.toml` file.
|
||||
|
||||
Note that hidden files (i.e., files and directories prefixed with a `.`) are _not_ ignored by
|
||||
default.
|
||||
|
||||
## 0.0.178
|
||||
|
||||
### Configuration files are now resolved hierarchically ([#1190](https://github.com/charliermarsh/ruff/pull/1190))
|
||||
|
||||
45
Cargo.lock
generated
45
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.178-dev.0"
|
||||
version = "0.0.184-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -894,6 +894,24 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
@@ -1827,7 +1845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1849,6 +1867,7 @@ dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"glob",
|
||||
"globset",
|
||||
"ignore",
|
||||
"insta",
|
||||
"itertools",
|
||||
"libcst",
|
||||
@@ -1876,12 +1895,13 @@ dependencies = [
|
||||
"titlecase",
|
||||
"toml",
|
||||
"update-informer",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1899,7 +1919,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1942,7 +1962,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -1952,7 +1972,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -1975,7 +1995,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -1992,7 +2012,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2304,6 +2324,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -30,6 +30,7 @@ fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
globset = { version = "0.4.9" }
|
||||
ignore = { version = "0.4.18" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
log = { version = "0.4.17" }
|
||||
@@ -42,11 +43,11 @@ quick-junit = { version = "0.3.2" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.178", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.184", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
@@ -70,6 +71,7 @@ assert_cmd = { version = "2.0.4" }
|
||||
criterion = { version = "0.4.0" }
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
ureq = { version = "2.5.0", features = [] }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
|
||||
132
README.md
132
README.md
@@ -86,6 +86,7 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
1. [flake8-builtins (A)](#flake8-builtins-a)
|
||||
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
|
||||
1. [flake8-debugger (T10)](#flake8-debugger-t10)
|
||||
1. [flake8-errmsg (EM)](#flake8-errmsg-em)
|
||||
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
|
||||
1. [flake8-print (T20)](#flake8-print-t20)
|
||||
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
||||
@@ -94,6 +95,7 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
|
||||
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
|
||||
1. [eradicate (ERA)](#eradicate-era)
|
||||
1. [pandas-vet (PDV)](#pandas-vet-pdv)
|
||||
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
|
||||
1. [Pylint (PLC, PLE, PLR, PLW)](#pylint-plc-ple-plr-plw)
|
||||
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
|
||||
@@ -155,7 +157,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.178
|
||||
rev: v0.0.184
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -304,13 +306,15 @@ Options:
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [default: text] [possible values: text, json, junit, grouped]
|
||||
Output serialization format for error messages [possible values: text, json, junit, grouped, github]
|
||||
--show-source
|
||||
Show violations with source code
|
||||
--respect-gitignore
|
||||
Respect file exclusions via `.gitignore` and other standard ignore files
|
||||
--show-files
|
||||
See the files Ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See Ruff's settings
|
||||
See the settings Ruff will use to check a given Python file
|
||||
--add-noqa
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
@@ -341,15 +345,18 @@ directory hierarchy is used for every individual file, with all paths in the `py
|
||||
|
||||
There are a few exceptions to these rules:
|
||||
|
||||
1. If a configuration file is passed directly via `--config`, those settings are used for across
|
||||
1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignore any
|
||||
`pyproject.toml` files that lack a `[tool.ruff]` section.
|
||||
2. If a configuration file is passed directly via `--config`, those settings are used for across
|
||||
files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are
|
||||
resolved relative to the _current working directory_.
|
||||
2. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
|
||||
a default configuration. If a user-specific configuration file exists at `${config_dir}/ruff/pyproject.toml`,
|
||||
that file will be used instead of the default configuration, with `${config_dir}` being determined
|
||||
via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all relative paths
|
||||
being again resolved relative to the _current working directory_.
|
||||
3. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
|
||||
3. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
|
||||
a default configuration. If a user-specific configuration file exists
|
||||
at `${config_dir}/ruff/pyproject.toml`,
|
||||
that file will be used instead of the default configuration, with `${config_dir}` being
|
||||
determined via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
|
||||
relative paths being again resolved relative to the _current working directory_.
|
||||
4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
|
||||
`--select`) will override the settings in _every_ resolved configuration file.
|
||||
|
||||
Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
|
||||
@@ -365,6 +372,18 @@ extend = "../pyproject.toml"
|
||||
line-length = 100
|
||||
```
|
||||
|
||||
### Python file discovery
|
||||
|
||||
When passed a path on the command-line, Ruff will automatically discover all Python files in that
|
||||
path, taking into account the [`exclude`](#exclude) and [`extend-exclude`](#extend-exclude) settings
|
||||
in each directory's `pyproject.toml` file.
|
||||
|
||||
By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](#respect-gitignore)).
|
||||
|
||||
Files that are passed to `ruff` directly are always checked, regardless of the above criteria.
|
||||
For example, `ruff /path/to/excluded/file.py` will always check `file.py`.
|
||||
|
||||
### Ignoring errors
|
||||
|
||||
To omit a lint check entirely, add it to the "ignore" list via [`ignore`](#ignore) or
|
||||
@@ -600,6 +619,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP013 | ConvertTypedDictFunctionalToClass | Convert `...` from `TypedDict` functional to class syntax | 🛠 |
|
||||
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
|
||||
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
|
||||
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -653,7 +673,7 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2
|
||||
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
|
||||
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
|
||||
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
|
||||
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
|
||||
| ANN204 | MissingReturnTypeSpecialMethod | Missing return type annotation for special method `...` | 🛠 |
|
||||
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
|
||||
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
|
||||
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
|
||||
@@ -765,6 +785,16 @@ For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/)
|
||||
| ---- | ---- | ------- | --- |
|
||||
| T100 | Debugger | Import for `...` found | |
|
||||
|
||||
### flake8-errmsg (EM)
|
||||
|
||||
For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/0.4.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| EM101 | RawStringInException | Exception must not use a string literal, assign to variable first | |
|
||||
| EM102 | FStringInException | Exception must not use an f-string literal, assign to variable first | |
|
||||
| EM103 | DotFormatInException | Exception must not use a `.format()` string directly, assign to variable first | |
|
||||
|
||||
### flake8-import-conventions (ICN)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
@@ -842,6 +872,25 @@ For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
|
||||
| ---- | ---- | ------- | --- |
|
||||
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
|
||||
|
||||
### pandas-vet (PDV)
|
||||
|
||||
For more, see [pandas-vet](https://pypi.org/project/pandas-vet/0.2.3/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PDV002 | UseOfInplaceArgument | `inplace=True` should be avoided; it has inconsistent behavior | |
|
||||
| PDV003 | UseOfDotIsNull | `.isna` is preferred to `.isnull`; functionality is equivalent | |
|
||||
| PDV004 | UseOfDotNotNull | `.notna` is preferred to `.notnull`; functionality is equivalent | |
|
||||
| PDV007 | UseOfDotIx | ``ix` i` deprecated; use more explicit `.loc` o` `.iloc` | |
|
||||
| PDV008 | UseOfDotAt | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
|
||||
| PDV009 | UseOfDotIat | Use `.iloc` instea` of `.iat`. If speed is important, use numpy. | |
|
||||
| PDV010 | UseOfDotPivotOrUnstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
|
||||
| PDV011 | UseOfDotValues | Use `.to_numpy()` instead of `.values` | |
|
||||
| PDV012 | UseOfDotReadTable | `.read_csv` is preferred to `.read_table`; provides same functionality | |
|
||||
| PDV013 | UseOfDotStack | `.melt` is preferred to `.stack`; provides same functionality | |
|
||||
| PDV015 | UseOfPdMerge | Use `.merge` method instead of `pd.merge` function. They have equivalent functionality. | |
|
||||
| PDV901 | DfIsABadVariableName | `df` is a bad variable name. Be kinder to your future self. | |
|
||||
|
||||
### pygrep-hooks (PGH)
|
||||
|
||||
For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub.
|
||||
@@ -1051,6 +1100,7 @@ natively, including:
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
|
||||
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
@@ -1073,8 +1123,7 @@ conflicts with the `isort` rules, like `I001`).
|
||||
|
||||
Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
pattern matching and parenthesized context managers.
|
||||
1. Ruff does not yet support structural pattern matching.
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules. (Instead, popular Flake8
|
||||
plugins are re-implemented in Rust as part of Ruff itself.)
|
||||
|
||||
@@ -1105,6 +1154,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
|
||||
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
@@ -1708,6 +1758,24 @@ any matching files.
|
||||
|
||||
---
|
||||
|
||||
#### [`respect-gitignore`](#respect-gitignore)
|
||||
|
||||
Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files. Enabled by default.
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
respect_gitignore = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`select`](#select)
|
||||
|
||||
A list of check code prefixes to enable. Prefixes can specify exact checks (like
|
||||
@@ -1921,6 +1989,25 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
|
||||
---
|
||||
|
||||
### `flake8-errmsg`
|
||||
|
||||
#### [`max-string-length`](#max-string-length)
|
||||
|
||||
Maximum string length for string literals in exception messages.
|
||||
|
||||
**Default value**: `0`
|
||||
|
||||
**Type**: `usize`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-errmsg]
|
||||
max-string-length = 20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `flake8-import-conventions`
|
||||
|
||||
#### [`aliases`](#aliases)
|
||||
@@ -2057,6 +2144,25 @@ ban-relative-imports = "all"
|
||||
|
||||
---
|
||||
|
||||
### `flake8-unused-arguments`
|
||||
|
||||
#### [`ignore-variadic-names`](#ignore-variadic-names)
|
||||
|
||||
Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-unused-arguments]
|
||||
ignore-variadic-names = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `isort`
|
||||
|
||||
#### [`combine-as-imports`](#combine-as-imports)
|
||||
|
||||
12
flake8_to_ruff/Cargo.lock
generated
12
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -2028,7 +2028,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2038,7 +2038,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2061,7 +2061,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2078,7 +2078,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.178-dev.0"
|
||||
version = "0.0.184-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -7,7 +7,8 @@ use ruff::flake8_tidy_imports::settings::Strictness;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming,
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_quotes, flake8_tidy_imports, mccabe,
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
@@ -73,6 +74,7 @@ pub fn convert(
|
||||
let mut options = Options::default();
|
||||
let mut flake8_annotations = flake8_annotations::settings::Options::default();
|
||||
let mut flake8_bugbear = flake8_bugbear::settings::Options::default();
|
||||
let mut flake8_errmsg = flake8_errmsg::settings::Options::default();
|
||||
let mut flake8_quotes = flake8_quotes::settings::Options::default();
|
||||
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
|
||||
let mut mccabe = mccabe::settings::Options::default();
|
||||
@@ -194,6 +196,15 @@ pub fn convert(
|
||||
Ok(max_complexity) => mccabe.max_complexity = Some(max_complexity),
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
},
|
||||
// flake8-errmsg
|
||||
"errmsg-max-string-length" | "errmsg_max_string_length" => {
|
||||
match value.clone().parse::<usize>() {
|
||||
Ok(max_string_length) => {
|
||||
flake8_errmsg.max_string_length = Some(max_string_length);
|
||||
}
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
}
|
||||
}
|
||||
// Unknown
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
@@ -209,6 +220,9 @@ pub fn convert(
|
||||
if flake8_bugbear != flake8_bugbear::settings::Options::default() {
|
||||
options.flake8_bugbear = Some(flake8_bugbear);
|
||||
}
|
||||
if flake8_errmsg != flake8_errmsg::settings::Options::default() {
|
||||
options.flake8_errmsg = Some(flake8_errmsg);
|
||||
}
|
||||
if flake8_quotes != flake8_quotes::settings::Options::default() {
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
@@ -258,6 +272,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -269,9 +284,11 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -304,6 +321,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -315,9 +333,11 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -350,6 +370,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -361,9 +382,11 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -396,6 +419,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -407,9 +431,11 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -442,6 +468,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -453,6 +480,7 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
@@ -461,6 +489,7 @@ mod tests {
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -496,6 +525,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
@@ -543,9 +573,11 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -578,6 +610,7 @@ mod tests {
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
per_file_ignores: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
@@ -590,6 +623,7 @@ mod tests {
|
||||
unfixable: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
@@ -598,6 +632,7 @@ mod tests {
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum Plugin {
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8Docstrings,
|
||||
Flake8ErrMsg,
|
||||
Flake8Eradicate,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
@@ -21,6 +22,7 @@ pub enum Plugin {
|
||||
Flake8Simplify,
|
||||
Flake8TidyImports,
|
||||
McCabe,
|
||||
PandasVet,
|
||||
PEP8Naming,
|
||||
Pyupgrade,
|
||||
}
|
||||
@@ -39,12 +41,14 @@ impl FromStr for Plugin {
|
||||
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
|
||||
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"flake8-return" => Ok(Plugin::Flake8Return),
|
||||
"flake8-simplify" => Ok(Plugin::Flake8Simplify),
|
||||
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
|
||||
"mccabe" => Ok(Plugin::McCabe),
|
||||
"pandas-vet" => Ok(Plugin::PandasVet),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||
_ => Err(anyhow!("Unknown plugin: {string}")),
|
||||
@@ -57,19 +61,24 @@ impl Plugin {
|
||||
match self {
|
||||
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
|
||||
Plugin::Flake8Bandit => CheckCodePrefix::S,
|
||||
// TODO(charlie): Handle rename of `B` to `BLE`.
|
||||
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
|
||||
Plugin::Flake8Bugbear => CheckCodePrefix::B,
|
||||
Plugin::Flake8Builtins => CheckCodePrefix::A,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
|
||||
Plugin::Flake8Debugger => CheckCodePrefix::T1,
|
||||
Plugin::Flake8Docstrings => CheckCodePrefix::D,
|
||||
// TODO(charlie): Handle rename of `E` to `ERA`.
|
||||
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
|
||||
Plugin::Flake8ErrMsg => CheckCodePrefix::EM,
|
||||
Plugin::Flake8Print => CheckCodePrefix::T2,
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Return => CheckCodePrefix::RET,
|
||||
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::McCabe => CheckCodePrefix::C9,
|
||||
// TODO(charlie): Handle rename of `PD` to `PDV`.
|
||||
Plugin::PandasVet => CheckCodePrefix::PDV,
|
||||
Plugin::PEP8Naming => CheckCodePrefix::N,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::U,
|
||||
}
|
||||
@@ -101,14 +110,16 @@ impl Plugin {
|
||||
DocstringConvention::PEP8.select()
|
||||
}
|
||||
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
|
||||
Plugin::Flake8ErrMsg => vec![CheckCodePrefix::EM],
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
Plugin::Flake8Simplify => vec![CheckCodePrefix::SIM],
|
||||
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
|
||||
Plugin::Flake8TidyImports => vec![CheckCodePrefix::TID],
|
||||
Plugin::McCabe => vec![CheckCodePrefix::C9],
|
||||
Plugin::PandasVet => vec![CheckCodePrefix::PDV],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::UP],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,6 +388,9 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
|
||||
"staticmethod-decorators" | "staticmethod_decorators" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
"max-string-length" | "max_string_length" => {
|
||||
plugins.insert(Plugin::Flake8ErrMsg);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -398,11 +412,13 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8Debugger,
|
||||
Plugin::Flake8Docstrings,
|
||||
Plugin::Flake8Eradicate,
|
||||
Plugin::Flake8ErrMsg,
|
||||
Plugin::Flake8Print,
|
||||
Plugin::Flake8Quotes,
|
||||
Plugin::Flake8Return,
|
||||
Plugin::Flake8Simplify,
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::PandasVet,
|
||||
Plugin::PEP8Naming,
|
||||
Plugin::Pyupgrade,
|
||||
]
|
||||
|
||||
@@ -20,6 +20,8 @@ getattr(foo, "_123abc")
|
||||
getattr(foo, "abc123")
|
||||
getattr(foo, r"abc123")
|
||||
_ = lambda x: getattr(x, "bar")
|
||||
if getattr(x, "bar"):
|
||||
pass
|
||||
|
||||
# Valid setattr usage
|
||||
setattr(foo, bar, None)
|
||||
@@ -28,6 +30,8 @@ setattr(foo, "123abc", None)
|
||||
setattr(foo, r"123\abc", None)
|
||||
setattr(foo, "except", None)
|
||||
_ = lambda x: setattr(x, "bar", 1)
|
||||
if setattr(x, "bar", 1):
|
||||
pass
|
||||
|
||||
# Invalid usage
|
||||
setattr(foo, "bar", None)
|
||||
|
||||
23
resources/test/fixtures/flake8_errmsg/EM.py
vendored
Normal file
23
resources/test/fixtures/flake8_errmsg/EM.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def f_a():
|
||||
raise RuntimeError("This is an example exception")
|
||||
|
||||
|
||||
def f_a_short():
|
||||
raise RuntimeError("Error")
|
||||
|
||||
|
||||
def f_b():
|
||||
example = "example"
|
||||
raise RuntimeError(f"This is an {example} exception")
|
||||
|
||||
|
||||
def f_c():
|
||||
raise RuntimeError("This is an {example} exception".format(example="example"))
|
||||
|
||||
|
||||
def f_ok():
|
||||
msg = "hello"
|
||||
raise RuntimeError(msg)
|
||||
@@ -27,7 +27,7 @@ def f(cls, x):
|
||||
lambda x: print("Hello, world!")
|
||||
|
||||
|
||||
class X:
|
||||
class C:
|
||||
###
|
||||
# Unused arguments.
|
||||
###
|
||||
|
||||
14
resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py
vendored
Normal file
14
resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
def f(a, b):
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
def f(a, b, *args, **kwargs):
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
class C:
|
||||
def f(self, a, b):
|
||||
print("Hello, world!")
|
||||
|
||||
def f(self, a, b, *args, **kwargs):
|
||||
print("Hello, world!")
|
||||
@@ -4,3 +4,10 @@ import os
|
||||
if True:
|
||||
x = 1; import sys
|
||||
import os
|
||||
|
||||
if True:
|
||||
x = 1; \
|
||||
import os
|
||||
|
||||
x = 1; \
|
||||
import os
|
||||
|
||||
39
resources/test/fixtures/pycodestyle/constant_literals.py
vendored
Normal file
39
resources/test/fixtures/pycodestyle/constant_literals.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
###
|
||||
# Errors
|
||||
###
|
||||
if "abc" is "def": # F632 (fix)
|
||||
pass
|
||||
if "abc" is None: # F632 (fix, but leaves behind unfixable E711)
|
||||
pass
|
||||
if None is "abc": # F632 (fix, but leaves behind unfixable E711)
|
||||
pass
|
||||
if "abc" is False: # F632 (fix, but leaves behind unfixable E712)
|
||||
pass
|
||||
if False is "abc": # F632 (fix, but leaves behind unfixable E712)
|
||||
pass
|
||||
if False == None: # E711, E712 (fix)
|
||||
pass
|
||||
if None == False: # E711, E712 (fix)
|
||||
pass
|
||||
|
||||
###
|
||||
# Unfixable errors
|
||||
###
|
||||
if "abc" == None: # E711
|
||||
pass
|
||||
if None == "abc": # E711
|
||||
pass
|
||||
if "abc" == False: # E712
|
||||
pass
|
||||
if False == "abc": # E712
|
||||
pass
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
if "def" == "abc":
|
||||
pass
|
||||
if False is None:
|
||||
pass
|
||||
if None is False:
|
||||
pass
|
||||
16
resources/test/fixtures/pyflakes/F401_7.py
vendored
Normal file
16
resources/test/fixtures/pyflakes/F401_7.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Test: noqa directives."""
|
||||
|
||||
from module import (
|
||||
A, # noqa: F401
|
||||
B,
|
||||
)
|
||||
|
||||
from module import (
|
||||
A, # noqa: F401
|
||||
B, # noqa: F401
|
||||
)
|
||||
|
||||
from module import (
|
||||
A,
|
||||
B,
|
||||
)
|
||||
1
resources/test/fixtures/pyflakes/F811_20.py
vendored
1
resources/test/fixtures/pyflakes/F811_20.py
vendored
@@ -7,7 +7,6 @@ import fu
|
||||
|
||||
|
||||
class bar:
|
||||
# STOPSHIP: This errors.
|
||||
fu = 1
|
||||
|
||||
|
||||
|
||||
40
resources/test/fixtures/pyflakes/F841_0.py
vendored
40
resources/test/fixtures/pyflakes/F841_0.py
vendored
@@ -10,13 +10,13 @@ except ValueError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def f1():
|
||||
def f():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
|
||||
|
||||
def f2():
|
||||
def f():
|
||||
foo = (1, 2)
|
||||
(a, b) = (1, 2)
|
||||
|
||||
@@ -26,12 +26,12 @@ def f2():
|
||||
(x, y) = baz = bar
|
||||
|
||||
|
||||
def f3():
|
||||
def f():
|
||||
locals()
|
||||
x = 1
|
||||
|
||||
|
||||
def f4():
|
||||
def f():
|
||||
_ = 1
|
||||
__ = 1
|
||||
_discarded = 1
|
||||
@@ -40,26 +40,26 @@ def f4():
|
||||
a = 1
|
||||
|
||||
|
||||
def f5():
|
||||
def f():
|
||||
global a
|
||||
|
||||
# Used in `f7` via `nonlocal`.
|
||||
# Used in `c` via `nonlocal`.
|
||||
b = 1
|
||||
|
||||
def f6():
|
||||
def c():
|
||||
# F841
|
||||
b = 1
|
||||
|
||||
def f7():
|
||||
def d():
|
||||
nonlocal b
|
||||
|
||||
|
||||
def f6():
|
||||
def f():
|
||||
annotations = []
|
||||
assert len([annotations for annotations in annotations])
|
||||
|
||||
|
||||
def f7():
|
||||
def f():
|
||||
def connect():
|
||||
return None, None
|
||||
|
||||
@@ -67,6 +67,22 @@ def f7():
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
|
||||
def f8():
|
||||
with open("file") as f, open("") as ((a, b)):
|
||||
def f():
|
||||
def connect():
|
||||
return None, None
|
||||
|
||||
with (connect() as (connection, cursor)):
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
|
||||
def f():
|
||||
with open("file") as my_file, open("") as ((this, that)):
|
||||
print("hello")
|
||||
|
||||
|
||||
def f():
|
||||
with (
|
||||
open("file") as my_file,
|
||||
open("") as ((this, that)),
|
||||
):
|
||||
print("hello")
|
||||
|
||||
51
resources/test/fixtures/pyflakes/multi_statement_lines.py
vendored
Normal file
51
resources/test/fixtures/pyflakes/multi_statement_lines.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
if True:
|
||||
import foo; x = 1
|
||||
import foo; x = 1
|
||||
|
||||
if True:
|
||||
import foo; \
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
import foo \
|
||||
; x = 1
|
||||
|
||||
|
||||
if True:
|
||||
x = 1; import foo
|
||||
|
||||
|
||||
if True:
|
||||
x = 1; \
|
||||
import foo
|
||||
|
||||
|
||||
if True:
|
||||
x = 1 \
|
||||
; import foo
|
||||
|
||||
|
||||
if True:
|
||||
x = 1; import foo; x = 1
|
||||
x = 1; import foo; x = 1
|
||||
|
||||
if True:
|
||||
x = 1; \
|
||||
import foo; \
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 1 \
|
||||
;import foo \
|
||||
;x = 1
|
||||
|
||||
|
||||
# Continuation, but not as the last content in the file.
|
||||
x = 1; \
|
||||
import foo
|
||||
|
||||
# Continuation, followed by end-of-file. (Removing `import foo` would cause a syntax
|
||||
# error.)
|
||||
x = 1; \
|
||||
import foo
|
||||
3
resources/test/fixtures/pyproject.toml
vendored
3
resources/test/fixtures/pyproject.toml
vendored
@@ -42,6 +42,9 @@ staticmethod-decorators = ["staticmethod"]
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
ban-relative-imports = "parents"
|
||||
|
||||
[tool.ruff.flake8-errmsg]
|
||||
max-string-length = 20
|
||||
|
||||
[tool.ruff.flake8-import-conventions.aliases]
|
||||
pandas = "pd"
|
||||
|
||||
|
||||
87
resources/test/fixtures/pyupgrade/UP016.py
vendored
Normal file
87
resources/test/fixtures/pyupgrade/UP016.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
# Replace names by built-in names, whether namespaced or not
|
||||
# https://github.com/search?q=%22from+six+import%22&type=code
|
||||
import six
|
||||
from six.moves import map # No need
|
||||
from six import text_type
|
||||
|
||||
six.text_type # str
|
||||
six.binary_type # bytes
|
||||
six.class_types # (type,)
|
||||
six.string_types # (str,)
|
||||
six.integer_types # (int,)
|
||||
six.unichr # chr
|
||||
six.iterbytes # iter
|
||||
six.print_(...) # print(...)
|
||||
six.exec_(c, g, l) # exec(c, g, l)
|
||||
six.advance_iterator(it) # next(it)
|
||||
six.next(it) # next(it)
|
||||
six.callable(x) # callable(x)
|
||||
six.moves.range(x) # range(x)
|
||||
six.moves.xrange(x) # range(x)
|
||||
isinstance(..., six.class_types) # isinstance(..., type)
|
||||
issubclass(..., six.integer_types) # issubclass(..., int)
|
||||
isinstance(..., six.string_types) # isinstance(..., str)
|
||||
|
||||
# Replace call on arg by method call on arg
|
||||
six.iteritems(dct) # dct.items()
|
||||
six.iterkeys(dct) # dct.keys()
|
||||
six.itervalues(dct) # dct.values()
|
||||
six.viewitems(dct) # dct.items()
|
||||
six.viewkeys(dct) # dct.keys()
|
||||
six.viewvalues(dct) # dct.values()
|
||||
six.assertCountEqual(self, a1, a2) # self.assertCountEqual(a1, a2)
|
||||
six.assertRaisesRegex(self, e, r, fn) # self.assertRaisesRegex(e, r, fn)
|
||||
six.assertRegex(self, s, r) # self.assertRegex(s, r)
|
||||
|
||||
# Replace call on arg by arg attribute
|
||||
six.get_method_function(meth) # meth.__func__
|
||||
six.get_method_self(meth) # meth.__self__
|
||||
six.get_function_closure(fn) # fn.__closure__
|
||||
six.get_function_code(fn) # fn.__code__
|
||||
six.get_function_defaults(fn) # fn.__defaults__
|
||||
six.get_function_globals(fn) # fn.__globals__
|
||||
|
||||
# Replace by string literal
|
||||
six.b("...") # b'...'
|
||||
six.u("...") # '...'
|
||||
six.ensure_binary("...") # b'...'
|
||||
six.ensure_str("...") # '...'
|
||||
six.ensure_text("...") # '...'
|
||||
six.b(string) # no change
|
||||
|
||||
# Replace by simple expression
|
||||
six.get_unbound_function(meth) # meth
|
||||
six.create_unbound_method(fn, cls) # fn
|
||||
|
||||
# Raise exception
|
||||
six.raise_from(exc, exc_from) # raise exc from exc_from
|
||||
six.reraise(tp, exc, tb) # raise exc.with_traceback(tb)
|
||||
six.reraise(*sys.exc_info()) # raise
|
||||
|
||||
# Int / Bytes conversion
|
||||
six.byte2int(bs) # bs[0]
|
||||
six.indexbytes(bs, i) # bs[i]
|
||||
six.int2byte(i) # bytes((i, ))
|
||||
|
||||
# Special cases for next calls
|
||||
next(six.iteritems(dct)) # next(iter(dct.items()))
|
||||
next(six.iterkeys(dct)) # next(iter(dct.keys()))
|
||||
next(six.itervalues(dct)) # next(iter(dct.values()))
|
||||
|
||||
# TODO: To implement
|
||||
|
||||
|
||||
# Rewrite classes
|
||||
@six.python_2_unicode_compatible # Remove
|
||||
class C(six.Iterator):
|
||||
pass # class C: pass
|
||||
|
||||
|
||||
class C(six.with_metaclass(M, B)):
|
||||
pass # class C(B, metaclass=M): pass
|
||||
|
||||
|
||||
# class C(B, metaclass=M): pass
|
||||
@six.add_metaclass(M)
|
||||
class C(B):
|
||||
pass
|
||||
1
resources/test/project/.gitignore
vendored
Normal file
1
resources/test/project/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
examples/generated
|
||||
@@ -9,33 +9,37 @@ Running from the repo root should pick up and enforce the appropriate settings f
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/
|
||||
Found 5 error(s).
|
||||
Found 7 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
3 potentially fixable with the --fix option.
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the project directory itself should exhibit the same behavior:
|
||||
|
||||
```
|
||||
∴ cd resources/test/project/ && cargo run .
|
||||
Found 5 error(s).
|
||||
∴ (cd resources/test/project/ && cargo run .)
|
||||
Found 7 error(s).
|
||||
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
src/file.py:1:8: F401 `os` imported but unused
|
||||
src/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
3 potentially fixable with the --fix option.
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
|
||||
files:
|
||||
|
||||
```
|
||||
∴ cd resources/test/project/examples/docs && cargo run .
|
||||
∴ (cd resources/test/project/examples/docs && cargo run .)
|
||||
Found 2 error(s).
|
||||
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
@@ -46,28 +50,40 @@ docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
file paths from the current working directory:
|
||||
|
||||
```
|
||||
∴ cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/
|
||||
Found 9 error(s).
|
||||
∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/)
|
||||
Found 11 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/examples/docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
6 potentially fixable with the --fix option.
|
||||
11 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets
|
||||
included in the output):
|
||||
|
||||
```
|
||||
∴ cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .
|
||||
Found 3 error(s).
|
||||
∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .)
|
||||
Found 4 error(s).
|
||||
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Passing an excluded directory directly should report errors in the contained files:
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/examples/excluded/
|
||||
Found 1 error(s).
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
2
resources/test/project/examples/.dotfiles/script.py
Executable file
2
resources/test/project/examples/.dotfiles/script.py
Executable file
@@ -0,0 +1,2 @@
|
||||
import numpy as np
|
||||
from app import app_file
|
||||
@@ -1,6 +1,7 @@
|
||||
[tool.ruff]
|
||||
extend = "../../pyproject.toml"
|
||||
src = ["."]
|
||||
extend-select = ["I001"]
|
||||
# Enable I001, and re-enable F841, to test extension priority.
|
||||
extend-select = ["I001", "F841"]
|
||||
extend-ignore = ["F401"]
|
||||
extend-exclude = ["./docs/concepts/file.py"]
|
||||
|
||||
5
resources/test/project/examples/excluded/script.py
Executable file
5
resources/test/project/examples/excluded/script.py
Executable file
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
@@ -1,3 +1,5 @@
|
||||
[tool.ruff]
|
||||
src = [".", "python_modules/*"]
|
||||
exclude = ["examples/excluded"]
|
||||
extend-select = ["I001"]
|
||||
extend-ignore = ["F841"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -11,8 +11,8 @@ itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.178"
|
||||
version = "0.0.184"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{
|
||||
Arguments, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
|
||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
|
||||
};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Create an `Expr` with default location from an `ExprKind`.
|
||||
pub fn create_expr(node: ExprKind) -> Expr {
|
||||
Expr::new(Location::default(), Location::default(), node)
|
||||
}
|
||||
|
||||
/// Create a `Stmt` with a default location from a `StmtKind`.
|
||||
pub fn create_stmt(node: StmtKind) -> Stmt {
|
||||
Stmt::new(Location::default(), Location::default(), node)
|
||||
}
|
||||
|
||||
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
@@ -149,15 +162,12 @@ pub fn match_call_path(
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
/// Return `true` if the `Stmt` is an assignment to a dunder (like `__all__`).
|
||||
pub fn is_assignment_to_a_dunder(stmt: &Stmt) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type
|
||||
// annotation. This is what pycodestyle (as of 2.9.1) does.
|
||||
match node {
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
value: _,
|
||||
type_comment: _,
|
||||
} => {
|
||||
match &stmt.node {
|
||||
StmtKind::Assign { targets, .. } => {
|
||||
if targets.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
@@ -166,12 +176,7 @@ pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation: _,
|
||||
value: _,
|
||||
simple: _,
|
||||
} => match &target.node {
|
||||
StmtKind::AnnAssign { target, .. } => match &target.node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
},
|
||||
@@ -179,6 +184,32 @@ pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a singleton (`None`, `True`, `False`, or
|
||||
/// `...`).
|
||||
pub fn is_singleton(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
expr.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None | Constant::Bool(_) | Constant::Ellipsis,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a constant or tuple of constants.
|
||||
pub fn is_constant(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Constant { .. } => true,
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a non-singleton constant.
|
||||
pub fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
|
||||
let mut handler_names = vec![];
|
||||
@@ -229,7 +260,6 @@ pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
// Check: is this a `super` call?
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
id == "super" && !args.is_empty()
|
||||
} else {
|
||||
@@ -311,13 +341,75 @@ pub fn count_trailing_lines(stmt: &Stmt, locator: &SourceCodeLocator) -> usize {
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Return the appropriate visual `Range` for any message that spans a `Stmt`.
|
||||
/// Specifically, this method returns the range of a function or class name,
|
||||
/// rather than that of the entire function or class body.
|
||||
pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
|
||||
if matches!(
|
||||
stmt.node,
|
||||
StmtKind::ClassDef { .. }
|
||||
| StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
) {
|
||||
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
if matches!(tok, Tok::Name { .. }) {
|
||||
let start = to_absolute(start, stmt.location);
|
||||
let end = to_absolute(end, stmt.location);
|
||||
return Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
};
|
||||
}
|
||||
}
|
||||
error!("Failed to find identifier for {:?}", stmt);
|
||||
}
|
||||
Range::from_located(stmt)
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` appears to be part of a multi-statement line, with
|
||||
/// other statements preceding it.
|
||||
pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
// Does the previous line end in a continuation? This will have a specific
|
||||
// false-positive, which is that if the previous line ends in a comment, it
|
||||
// will be treated as a continuation. So we should only use this information to
|
||||
// make conservative choices.
|
||||
// TODO(charlie): Come up with a more robust strategy.
|
||||
if stmt.location.row() > 1 {
|
||||
let range = Range {
|
||||
location: Location::new(stmt.location.row() - 1, 0),
|
||||
end_location: Location::new(stmt.location.row(), 0),
|
||||
};
|
||||
let line = locator.slice_source_code_range(&range);
|
||||
if line.trim().ends_with('\\') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` appears to be part of a multi-statement line, with
|
||||
/// other statements preceding it.
|
||||
pub fn preceded_by_multi_statement_line(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
match_leading_content(stmt, locator) || preceded_by_continuation(stmt, locator)
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` appears to be part of a multi-statement line, with
|
||||
/// other statements following it.
|
||||
pub fn followed_by_multi_statement_line(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
match_trailing_content(stmt, locator)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::helpers::{identifier_range, match_module_member, match_trailing_content};
|
||||
use crate::ast::types::Range;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn builtin() -> Result<()> {
|
||||
@@ -461,4 +553,130 @@ mod tests {
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_content() -> Result<()> {
|
||||
let contents = "x = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert!(!match_trailing_content(stmt, &locator));
|
||||
|
||||
let contents = "x = 1; y = 2";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert!(match_trailing_content(stmt, &locator));
|
||||
|
||||
let contents = "x = 1 ";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert!(!match_trailing_content(stmt, &locator));
|
||||
|
||||
let contents = "x = 1 # Comment";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert!(!match_trailing_content(stmt, &locator));
|
||||
|
||||
let contents = r#"
|
||||
x = 1
|
||||
y = 2
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert!(!match_trailing_content(stmt, &locator));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_identifier_range() -> Result<()> {
|
||||
let contents = "def f(): pass".trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 4),
|
||||
end_location: Location::new(1, 5),
|
||||
}
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
def \
|
||||
f():
|
||||
pass
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(2, 2),
|
||||
end_location: Location::new(2, 3),
|
||||
}
|
||||
);
|
||||
|
||||
let contents = "class Class(): pass".trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 6),
|
||||
end_location: Location::new(1, 11),
|
||||
}
|
||||
);
|
||||
|
||||
let contents = "class Class: pass".trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 6),
|
||||
end_location: Location::new(1, 11),
|
||||
}
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@decorator()
|
||||
class Class():
|
||||
pass
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(2, 6),
|
||||
end_location: Location::new(2, 11),
|
||||
}
|
||||
);
|
||||
|
||||
let contents = r#"x = y + 1"#.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 9),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,6 @@ impl From<bool> for Mode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Mode> for bool {
|
||||
fn from(value: &Mode) -> Self {
|
||||
match value {
|
||||
Mode::Generate | Mode::Apply => true,
|
||||
Mode::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file<'a>(
|
||||
checks: &'a [Check],
|
||||
|
||||
@@ -2,7 +2,11 @@ use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::{ExcepthandlerKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::helpers::to_absolute;
|
||||
use crate::ast::whitespace::LinesWithTrailingNewline;
|
||||
use crate::autofix::Fix;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
@@ -66,7 +70,87 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
|
||||
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
|
||||
/// of a multi-statement line.
|
||||
fn trailing_semicolon(stmt: &Stmt, locator: &SourceCodeLocator) -> Option<Location> {
|
||||
let contents = locator.slice_source_code_at(&stmt.end_location.unwrap());
|
||||
for (row, line) in LinesWithTrailingNewline::from(&contents).enumerate() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with(';') {
|
||||
let column = line
|
||||
.char_indices()
|
||||
.find_map(|(column, char)| if char == ';' { Some(column) } else { None })
|
||||
.unwrap();
|
||||
return Some(to_absolute(
|
||||
Location::new(row + 1, column),
|
||||
stmt.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
if !trimmed.starts_with('\\') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the next valid break for a `Stmt` after a semicolon.
|
||||
fn next_stmt_break(semicolon: Location, locator: &SourceCodeLocator) -> Location {
|
||||
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
|
||||
let contents = locator.slice_source_code_at(&start_location);
|
||||
for (row, line) in LinesWithTrailingNewline::from(&contents).enumerate() {
|
||||
let trimmed = line.trim();
|
||||
// Skip past any continuations.
|
||||
if trimmed.starts_with('\\') {
|
||||
continue;
|
||||
}
|
||||
return if trimmed.is_empty() {
|
||||
// If the line is empty, then despite the previous statement ending in a
|
||||
// semicolon, we know that it's not a multi-statement line.
|
||||
to_absolute(Location::new(row + 1, 0), start_location)
|
||||
} else {
|
||||
// Otherwise, find the start of the next statement. (Or, anything that isn't
|
||||
// whitespace.)
|
||||
let column = line
|
||||
.char_indices()
|
||||
.find_map(|(column, char)| {
|
||||
if char.is_whitespace() {
|
||||
None
|
||||
} else {
|
||||
Some(column)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
to_absolute(Location::new(row + 1, column), start_location)
|
||||
};
|
||||
}
|
||||
Location::new(start_location.row() + 1, 0)
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` occurs at the end of a file.
|
||||
fn is_end_of_file(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
let contents = locator.slice_source_code_at(&stmt.end_location.unwrap());
|
||||
contents.is_empty()
|
||||
}
|
||||
|
||||
/// Return the `Fix` to use when deleting a `Stmt`.
|
||||
///
|
||||
/// In some cases, this is as simple as deleting the `Range` of the `Stmt`
|
||||
/// itself. However, there are a few exceptions:
|
||||
/// - If the `Stmt` is _not_ the terminal statement in a multi-statement line,
|
||||
/// we need to delete up to the start of the next statement (and avoid
|
||||
/// deleting any content that precedes the statement).
|
||||
/// - If the `Stmt` is the terminal statement in a multi-statement line, we need
|
||||
/// to avoid deleting any content that precedes the statement.
|
||||
/// - If the `Stmt` has no trailing and leading content, then it's convenient to
|
||||
/// remove the entire start and end lines.
|
||||
/// - If the `Stmt` is the last statement in its parent body, replace it with a
|
||||
/// `pass` instead.
|
||||
pub fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &SourceCodeLocator,
|
||||
) -> Result<Fix> {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
@@ -80,12 +164,103 @@ pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Res
|
||||
stmt.end_location.unwrap(),
|
||||
))
|
||||
} else {
|
||||
// Otherwise, nuke the entire line.
|
||||
// TODO(charlie): This logic assumes that there are no multi-statement physical
|
||||
// lines.
|
||||
Ok(Fix::deletion(
|
||||
Location::new(stmt.location.row(), 0),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
))
|
||||
Ok(if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
let next = next_stmt_break(semicolon, locator);
|
||||
Fix::deletion(stmt.location, next)
|
||||
} else if helpers::match_leading_content(stmt, locator) {
|
||||
Fix::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
} else if helpers::preceded_by_continuation(stmt, locator) {
|
||||
if is_end_of_file(stmt, locator) && stmt.location.column() == 0 {
|
||||
// Special-case: a file can't end in a continuation.
|
||||
Fix::replacement("\n".to_string(), stmt.location, stmt.end_location.unwrap())
|
||||
} else {
|
||||
Fix::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
}
|
||||
} else {
|
||||
Fix::deletion(
|
||||
Location::new(stmt.location.row(), 0),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::autofix::helpers::{next_stmt_break, trailing_semicolon};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
let contents = "x = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt, &locator), None);
|
||||
|
||||
let contents = "x = 1; y = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
trailing_semicolon(stmt, &locator),
|
||||
Some(Location::new(1, 5))
|
||||
);
|
||||
|
||||
let contents = "x = 1 ; y = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
trailing_semicolon(stmt, &locator),
|
||||
Some(Location::new(1, 6))
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
x = 1 \
|
||||
; y = 1
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
trailing_semicolon(stmt, &locator),
|
||||
Some(Location::new(2, 2))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_next_stmt_break() {
|
||||
let contents = "x = 1; y = 1";
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
next_stmt_break(Location::new(1, 4), &locator),
|
||||
Location::new(1, 5)
|
||||
);
|
||||
|
||||
let contents = "x = 1 ; y = 1";
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
next_stmt_break(Location::new(1, 5), &locator),
|
||||
Location::new(1, 6)
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
x = 1 \
|
||||
; y = 1
|
||||
"#
|
||||
.trim();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
next_stmt_break(Location::new(2, 2), &locator),
|
||||
Location::new(2, 4)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,4 @@ impl Fix {
|
||||
end_location: at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dummy(location: Location) -> Self {
|
||||
Self {
|
||||
content: String::new(),
|
||||
location,
|
||||
end_location: location,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
273
src/check_ast.rs
273
src/check_ast.rs
@@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||
use log::error;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_ast::{Located, Location};
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -25,21 +25,22 @@ use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, DeferralKeyword};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable};
|
||||
use crate::noqa::Directive;
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
|
||||
flake8_import_conventions, flake8_print, flake8_return, flake8_simplify, flake8_tidy_imports,
|
||||
flake8_unused_arguments, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks,
|
||||
pylint, pyupgrade, visibility,
|
||||
flake8_errmsg, flake8_import_conventions, flake8_print, flake8_return, flake8_simplify,
|
||||
flake8_tidy_imports, flake8_unused_arguments, mccabe, noqa, pandas_vet, pep8_naming,
|
||||
pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, visibility,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -50,8 +51,10 @@ type DeferralContext<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
path: &'a Path,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) noqa_line_for: &'a IntMap<usize, usize>,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
@@ -98,13 +101,17 @@ pub struct Checker<'a> {
|
||||
impl<'a> Checker<'a> {
|
||||
pub fn new(
|
||||
settings: &'a Settings,
|
||||
autofix: bool,
|
||||
noqa_line_for: &'a IntMap<usize, usize>,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
path: &'a Path,
|
||||
locator: &'a SourceCodeLocator,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
noqa_line_for,
|
||||
autofix,
|
||||
noqa,
|
||||
path,
|
||||
locator,
|
||||
checks: vec![],
|
||||
@@ -175,7 +182,9 @@ impl<'a> Checker<'a> {
|
||||
pub fn patch(&self, code: &CheckCode) -> bool {
|
||||
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
|
||||
// location data.
|
||||
self.autofix && self.in_f_string.is_none() && self.settings.fixable.contains(code)
|
||||
matches!(self.autofix, flags::Autofix::Enabled)
|
||||
&& self.in_f_string.is_none()
|
||||
&& self.settings.fixable.contains(code)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
@@ -199,6 +208,30 @@ impl<'a> Checker<'a> {
|
||||
matches!(self.bindings[*index].kind, BindingKind::Builtin)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if a `CheckCode` is disabled by a `noqa` directive.
|
||||
pub fn is_ignored(&self, code: &CheckCode, lineno: usize) -> bool {
|
||||
// TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`.
|
||||
// However, in rare cases, we need to check them here. For example, when
|
||||
// removing unused imports, we create a single fix that's applied to all
|
||||
// unused members on a single import. We need to pre-emptively omit any
|
||||
// members from the fix that will eventually be excluded by a `noqa`.
|
||||
// Unfortunately, we _do_ want to register a `Check` for each eventually-ignored
|
||||
// import, so that our `noqa` counts are accurate.
|
||||
if matches!(self.noqa, flags::Noqa::Disabled) {
|
||||
return false;
|
||||
}
|
||||
let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
let line = self.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(*noqa_lineno, 0),
|
||||
end_location: Location::new(noqa_lineno + 1, 0),
|
||||
});
|
||||
match noqa::extract_noqa_directive(&line) {
|
||||
Directive::None => false,
|
||||
Directive::All(..) => true,
|
||||
Directive::Codes(.., codes) => noqa::includes(code, &codes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
@@ -223,10 +256,10 @@ where
|
||||
StmtKind::Import { .. } => {
|
||||
self.futures_allowed = false;
|
||||
}
|
||||
node => {
|
||||
_ => {
|
||||
self.futures_allowed = false;
|
||||
if !self.seen_import_boundary
|
||||
&& !helpers::is_assignment_to_a_dunder(node)
|
||||
&& !helpers::is_assignment_to_a_dunder(stmt)
|
||||
&& !operations::in_nested_block(
|
||||
&mut self.parents.iter().rev().map(|node| node.0),
|
||||
)
|
||||
@@ -257,12 +290,11 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
let location = Range::from_located(stmt);
|
||||
self.add_checks(
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, location)
|
||||
pycodestyle::checks::ambiguous_variable_name(name, stmt)
|
||||
})
|
||||
.into_iter(),
|
||||
);
|
||||
@@ -309,12 +341,11 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
let location = Range::from_located(stmt);
|
||||
self.add_checks(
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, location)
|
||||
pycodestyle::checks::ambiguous_variable_name(name, stmt)
|
||||
})
|
||||
.into_iter(),
|
||||
);
|
||||
@@ -369,7 +400,8 @@ where
|
||||
if let Some(check) = pep8_naming::checks::invalid_function_name(
|
||||
stmt,
|
||||
name,
|
||||
&self.settings.pep8_naming,
|
||||
&self.settings.pep8_naming.ignore_names,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -406,9 +438,12 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N807) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::dunder_function_name(self.current_scope(), stmt, name)
|
||||
{
|
||||
if let Some(check) = pep8_naming::checks::dunder_function_name(
|
||||
self.current_scope(),
|
||||
stmt,
|
||||
name,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -445,6 +480,7 @@ where
|
||||
name,
|
||||
body,
|
||||
self.settings.mccabe.max_complexity,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -460,7 +496,7 @@ where
|
||||
pylint::plugins::property_with_parameters(self, stmt, decorator_list, args);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
|
||||
self.check_builtin_shadowing(name, stmt, true);
|
||||
|
||||
// Visit the decorators and arguments, but avoid the body, which will be
|
||||
// deferred.
|
||||
@@ -548,7 +584,9 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N801) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_class_name(stmt, name) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::invalid_class_name(stmt, name, self.locator)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -573,7 +611,7 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), false);
|
||||
self.check_builtin_shadowing(name, stmt, false);
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr);
|
||||
@@ -615,7 +653,7 @@ where
|
||||
);
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
self.check_builtin_shadowing(asname, stmt, false);
|
||||
}
|
||||
|
||||
// Given `import foo`, `name` and `full_name` would both be `foo`.
|
||||
@@ -683,7 +721,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::N811) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::constant_imported_as_non_constant(
|
||||
stmt, name, asname,
|
||||
stmt,
|
||||
name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -693,7 +734,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::N812) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::lowercase_imported_as_non_lowercase(
|
||||
stmt, name, asname,
|
||||
stmt,
|
||||
name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -703,7 +747,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::N813) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::camelcase_imported_as_lowercase(
|
||||
stmt, name, asname,
|
||||
stmt,
|
||||
name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -712,7 +759,10 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N814) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
|
||||
stmt, name, asname,
|
||||
stmt,
|
||||
name,
|
||||
asname,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -720,7 +770,10 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N817) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
|
||||
stmt, name, asname,
|
||||
stmt,
|
||||
name,
|
||||
asname,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -858,7 +911,7 @@ where
|
||||
scope.import_starred = true;
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
self.check_builtin_shadowing(asname, stmt, false);
|
||||
}
|
||||
|
||||
// Given `from foo import bar`, `name` would be "bar" and `full_name` would
|
||||
@@ -927,6 +980,7 @@ where
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -939,6 +993,7 @@ where
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -951,6 +1006,7 @@ where
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
self.locator,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
@@ -962,6 +1018,7 @@ where
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -972,6 +1029,7 @@ where
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -995,6 +1053,20 @@ where
|
||||
flake8_bugbear::plugins::cannot_raise_literal(self, exc);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::EM101)
|
||||
| self.settings.enabled.contains(&CheckCode::EM102)
|
||||
| self.settings.enabled.contains(&CheckCode::EM103)
|
||||
{
|
||||
if let Some(exc) = exc {
|
||||
self.add_checks(
|
||||
flake8_errmsg::checks::check_string_in_exception(
|
||||
exc,
|
||||
self.settings.flake8_errmsg.max_string_length,
|
||||
)
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => {
|
||||
self.handle_node_load(target);
|
||||
@@ -1107,6 +1179,11 @@ where
|
||||
self, stmt, targets, value,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::PDV901) {
|
||||
if let Some(check) = pandas_vet::checks::assignment_to_df(targets) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -1422,15 +1499,14 @@ where
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
id,
|
||||
Range::from_located(expr),
|
||||
) {
|
||||
if let Some(check) =
|
||||
pycodestyle::checks::ambiguous_variable_name(id, expr)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(id, Range::from_located(expr), true);
|
||||
self.check_builtin_shadowing(id, expr, true);
|
||||
|
||||
self.handle_node_store(id, expr);
|
||||
}
|
||||
@@ -1458,9 +1534,26 @@ where
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP016) {
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PDV007, "ix"),
|
||||
(CheckCode::PDV008, "at"),
|
||||
(CheckCode::PDV009, "iat"),
|
||||
(CheckCode::PDV011, "values"),
|
||||
] {
|
||||
if self.settings.enabled.contains(&code) {
|
||||
if attr == name {
|
||||
self.add_check(Check::new(code.kind(), Range::from_located(expr)));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
func,
|
||||
@@ -1533,6 +1626,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP012) {
|
||||
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP016) {
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
@@ -1557,14 +1653,7 @@ where
|
||||
flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B010) {
|
||||
if !self
|
||||
.scope_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda(..)))
|
||||
{
|
||||
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B022) {
|
||||
flake8_bugbear::plugins::useless_contextlib_suppress(self, expr, args);
|
||||
@@ -1838,6 +1927,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// pandas-vet
|
||||
if self.settings.enabled.contains(&CheckCode::PDV002) {
|
||||
self.add_checks(pandas_vet::checks::inplace_argument(keywords).into_iter());
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PDV003, "isnull"),
|
||||
(CheckCode::PDV004, "notnull"),
|
||||
(CheckCode::PDV010, "pivot"),
|
||||
(CheckCode::PDV010, "unstack"),
|
||||
(CheckCode::PDV012, "read_table"),
|
||||
(CheckCode::PDV013, "stack"),
|
||||
] {
|
||||
if self.settings.enabled.contains(&code) {
|
||||
if let ExprKind::Attribute { attr, .. } = &func.node {
|
||||
if attr == name {
|
||||
self.add_check(Check::new(code.kind(), Range::from_located(func)));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PDV015) {
|
||||
if let Some(check) = pandas_vet::checks::use_of_pd_merge(func) {
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
// pygrep-hooks
|
||||
if self.settings.enabled.contains(&CheckCode::PGH001) {
|
||||
pygrep_hooks::checks::no_eval(self, func);
|
||||
@@ -2420,19 +2537,14 @@ where
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
name,
|
||||
Range::from_located(excepthandler),
|
||||
) {
|
||||
if let Some(check) =
|
||||
pycodestyle::checks::ambiguous_variable_name(name, excepthandler)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
Range::from_located(excepthandler),
|
||||
false,
|
||||
);
|
||||
self.check_builtin_shadowing(name, excepthandler, false);
|
||||
|
||||
if self.current_scope().values.contains_key(&name.as_str()) {
|
||||
self.handle_node_store(
|
||||
@@ -2545,23 +2657,18 @@ where
|
||||
);
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
&arg.node.arg,
|
||||
Range::from_located(arg),
|
||||
) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(&arg.node.arg, arg) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N803) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::invalid_argument_name(&arg.node.arg, Range::from_located(arg))
|
||||
{
|
||||
if let Some(check) = pep8_naming::checks::invalid_argument_name(&arg.node.arg, arg) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3322,6 +3429,8 @@ impl<'a> Checker<'a> {
|
||||
(&'a RefEquality<'b, Stmt>, Option<&'a RefEquality<'b, Stmt>>);
|
||||
|
||||
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
|
||||
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
|
||||
FxHashMap::default();
|
||||
|
||||
for (name, index) in scope
|
||||
.values
|
||||
@@ -3346,12 +3455,21 @@ impl<'a> Checker<'a> {
|
||||
|
||||
let defined_by = binding.source.as_ref().unwrap();
|
||||
let defined_in = self.child_to_parent.get(defined_by);
|
||||
unused
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
if self.is_ignored(&CheckCode::F401, binding.range.location.row()) {
|
||||
ignored
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
} else {
|
||||
unused
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
}
|
||||
}
|
||||
|
||||
let ignore_init =
|
||||
self.settings.ignore_init_module_imports && self.path.ends_with("__init__.py");
|
||||
for ((defined_by, defined_in), unused_imports) in unused
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, _), _)| defined_by.0.location)
|
||||
@@ -3359,17 +3477,15 @@ impl<'a> Checker<'a> {
|
||||
let child = defined_by.0;
|
||||
let parent = defined_in.map(|defined_in| defined_in.0);
|
||||
|
||||
let ignore_init = self.settings.ignore_init_module_imports
|
||||
&& self.path.ends_with("__init__.py");
|
||||
let fix = if !ignore_init && self.patch(&CheckCode::F401) {
|
||||
let deleted: Vec<&Stmt> =
|
||||
self.deletions.iter().map(|node| node.0).collect();
|
||||
match pyflakes::fixes::remove_unused_imports(
|
||||
self.locator,
|
||||
&unused_imports,
|
||||
child,
|
||||
parent,
|
||||
&deleted,
|
||||
self.locator,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
@@ -3397,6 +3513,17 @@ impl<'a> Checker<'a> {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
for (_, unused_imports) in ignored
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, _), _)| defined_by.0.location)
|
||||
{
|
||||
for (full_name, range) in unused_imports {
|
||||
checks.push(Check::new(
|
||||
CheckKind::UnusedImport(full_name.clone(), ignore_init),
|
||||
*range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.add_checks(checks.into_iter());
|
||||
@@ -3591,12 +3718,12 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
|
||||
fn check_builtin_shadowing<T>(&mut self, name: &str, located: &Located<T>, is_attribute: bool) {
|
||||
if is_attribute && matches!(self.current_scope().kind, ScopeKind::Class(_)) {
|
||||
if self.settings.enabled.contains(&CheckCode::A003) {
|
||||
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
|
||||
name,
|
||||
location,
|
||||
located,
|
||||
flake8_builtins::types::ShadowingType::Attribute,
|
||||
) {
|
||||
self.add_check(check);
|
||||
@@ -3606,7 +3733,7 @@ impl<'a> Checker<'a> {
|
||||
if self.settings.enabled.contains(&CheckCode::A001) {
|
||||
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
|
||||
name,
|
||||
location,
|
||||
located,
|
||||
flake8_builtins::types::ShadowingType::Variable,
|
||||
) {
|
||||
self.add_check(check);
|
||||
@@ -3615,11 +3742,11 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) {
|
||||
fn check_builtin_arg_shadowing(&mut self, name: &str, arg: &Arg) {
|
||||
if self.settings.enabled.contains(&CheckCode::A002) {
|
||||
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
|
||||
name,
|
||||
location,
|
||||
arg,
|
||||
flake8_builtins::types::ShadowingType::Argument,
|
||||
) {
|
||||
self.add_check(check);
|
||||
@@ -3631,11 +3758,13 @@ impl<'a> Checker<'a> {
|
||||
pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, autofix, path, locator);
|
||||
let mut checker = Checker::new(settings, noqa_line_for, autofix, noqa, path, locator);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ use crate::checks::Check;
|
||||
use crate::directives::IsortDirectives;
|
||||
use crate::isort;
|
||||
use crate::isort::track::ImportTracker;
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn check_import_blocks(
|
||||
tracker: ImportTracker,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
for block in tracker.into_iter() {
|
||||
@@ -34,7 +34,7 @@ pub fn check_imports(
|
||||
locator: &SourceCodeLocator,
|
||||
directives: &IsortDirectives,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut tracker = ImportTracker::new(locator, directives, path);
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::autofix::Fix;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
|
||||
use crate::noqa;
|
||||
use crate::noqa::{is_file_exempt, Directive};
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
// Regex from PEP263.
|
||||
static CODING_COMMENT_REGEX: Lazy<Regex> =
|
||||
@@ -39,8 +39,8 @@ pub fn check_lines(
|
||||
contents: &str,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
ignore_noqa: bool,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
) {
|
||||
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::UP009);
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
@@ -64,14 +64,14 @@ pub fn check_lines(
|
||||
{
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push($check.kind.code().as_ref());
|
||||
if ignore_noqa {
|
||||
if matches!(noqa, flags::Noqa::Disabled) {
|
||||
line_checks.push($check);
|
||||
}
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if noqa::includes($check.kind.code(), codes) {
|
||||
matches.push($check.kind.code().as_ref());
|
||||
if ignore_noqa {
|
||||
if matches!(noqa, flags::Noqa::Disabled) {
|
||||
line_checks.push($check);
|
||||
}
|
||||
} else {
|
||||
@@ -108,7 +108,9 @@ pub fn check_lines(
|
||||
end_location: Location::new(lineno + 2, 0),
|
||||
},
|
||||
);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
{
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(lineno + 1, 0),
|
||||
Location::new(lineno + 2, 0),
|
||||
@@ -155,7 +157,7 @@ pub fn check_lines(
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Range {
|
||||
location: Location::new(lineno + 1, 0),
|
||||
location: Location::new(lineno + 1, settings.line_length),
|
||||
end_location: Location::new(lineno + 1, line_length),
|
||||
},
|
||||
);
|
||||
@@ -196,7 +198,9 @@ pub fn check_lines(
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
{
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start - spaces),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
@@ -225,7 +229,9 @@ pub fn check_lines(
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
{
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start - spaces),
|
||||
@@ -247,7 +253,7 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
if !ignore_noqa {
|
||||
if matches!(noqa, flags::Noqa::Enabled) {
|
||||
ignored.sort_unstable();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.swap_remove(*index);
|
||||
@@ -262,7 +268,7 @@ mod tests {
|
||||
|
||||
use super::check_lines;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
@@ -277,8 +283,8 @@ mod tests {
|
||||
line_length,
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
true,
|
||||
false,
|
||||
flags::Autofix::Enabled,
|
||||
flags::Noqa::Enabled,
|
||||
);
|
||||
checks
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ use rustpython_parser::lexer::{LexResult, Tok};
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::ruff::checks::Context;
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
|
||||
|
||||
@@ -12,7 +13,7 @@ pub fn check_tokens(
|
||||
locator: &SourceCodeLocator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
|
||||
163
src/checks.rs
163
src/checks.rs
@@ -223,6 +223,7 @@ pub enum CheckCode {
|
||||
UP013,
|
||||
UP014,
|
||||
UP015,
|
||||
UP016,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -315,6 +316,23 @@ pub enum CheckCode {
|
||||
RUF100,
|
||||
// pygrep-hooks
|
||||
PGH001,
|
||||
// pandas-vet
|
||||
PDV002,
|
||||
PDV003,
|
||||
PDV004,
|
||||
PDV007,
|
||||
PDV008,
|
||||
PDV009,
|
||||
PDV010,
|
||||
PDV011,
|
||||
PDV012,
|
||||
PDV013,
|
||||
PDV015,
|
||||
PDV901,
|
||||
// flake8-errmsg
|
||||
EM101,
|
||||
EM102,
|
||||
EM103,
|
||||
}
|
||||
|
||||
#[derive(EnumIter, Debug, PartialEq, Eq)]
|
||||
@@ -335,6 +353,7 @@ pub enum CheckCategory {
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8ErrMsg,
|
||||
Flake8ImportConventions,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
@@ -343,6 +362,7 @@ pub enum CheckCategory {
|
||||
Flake8TidyImports,
|
||||
Flake8UnusedArguments,
|
||||
Eradicate,
|
||||
PandasVet,
|
||||
PygrepHooks,
|
||||
Pylint,
|
||||
Ruff,
|
||||
@@ -375,6 +395,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Debugger => "flake8-debugger",
|
||||
CheckCategory::Flake8ErrMsg => "flake8-errmsg",
|
||||
CheckCategory::Flake8ImportConventions => "flake8-import-conventions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
@@ -384,6 +405,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8UnusedArguments => "flake8-unused-arguments",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::McCabe => "mccabe",
|
||||
CheckCategory::PandasVet => "pandas-vet",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
CheckCategory::Pycodestyle => "pycodestyle",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
@@ -407,6 +429,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
CheckCategory::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
CheckCategory::Flake8Debugger => vec![CheckCodePrefix::T10],
|
||||
CheckCategory::Flake8ErrMsg => vec![CheckCodePrefix::EM],
|
||||
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
|
||||
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
@@ -415,6 +438,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
|
||||
CheckCategory::Isort => vec![CheckCodePrefix::I],
|
||||
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
|
||||
CheckCategory::PandasVet => vec![CheckCodePrefix::PDV],
|
||||
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
|
||||
CheckCategory::Pydocstyle => vec![CheckCodePrefix::D],
|
||||
@@ -473,6 +497,10 @@ impl CheckCategory {
|
||||
"https://pypi.org/project/flake8-debugger/4.1.2/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8ErrMsg => Some((
|
||||
"https://pypi.org/project/flake8-errmsg/0.4.0/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8ImportConventions => None,
|
||||
CheckCategory::Flake8Print => Some((
|
||||
"https://pypi.org/project/flake8-print/5.0.0/",
|
||||
@@ -504,6 +532,10 @@ impl CheckCategory {
|
||||
CheckCategory::McCabe => {
|
||||
Some(("https://pypi.org/project/mccabe/0.7.0/", &Platform::PyPI))
|
||||
}
|
||||
CheckCategory::PandasVet => Some((
|
||||
"https://pypi.org/project/pandas-vet/0.2.3/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::PEP8Naming => Some((
|
||||
"https://pypi.org/project/pep8-naming/0.13.2/",
|
||||
&Platform::PyPI,
|
||||
@@ -740,7 +772,7 @@ pub enum CheckKind {
|
||||
MissingTypeCls(String),
|
||||
MissingReturnTypePublicFunction(String),
|
||||
MissingReturnTypePrivateFunction(String),
|
||||
MissingReturnTypeMagicMethod(String),
|
||||
MissingReturnTypeSpecialMethod(String),
|
||||
MissingReturnTypeStaticMethod(String),
|
||||
MissingReturnTypeClassMethod(String),
|
||||
DynamicallyTypedExpression(String),
|
||||
@@ -772,6 +804,7 @@ pub enum CheckKind {
|
||||
ConvertTypedDictFunctionalToClass(String),
|
||||
ConvertNamedTupleFunctionalToClass(String),
|
||||
RedundantOpenModes,
|
||||
RemoveSixCompat,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -861,6 +894,23 @@ pub enum CheckKind {
|
||||
UnusedLambdaArgument(String),
|
||||
// flake8-import-conventions
|
||||
ImportAliasIsNotConventional(String, String),
|
||||
// pandas-vet
|
||||
UseOfInplaceArgument,
|
||||
UseOfDotIsNull,
|
||||
UseOfDotNotNull,
|
||||
UseOfDotIx,
|
||||
UseOfDotAt,
|
||||
UseOfDotIat,
|
||||
UseOfDotPivotOrUnstack,
|
||||
UseOfDotValues,
|
||||
UseOfDotReadTable,
|
||||
UseOfDotStack,
|
||||
UseOfPdMerge,
|
||||
DfIsABadVariableName,
|
||||
// flake8-errmsg
|
||||
RawStringInException,
|
||||
FStringInException,
|
||||
DotFormatInException,
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
@@ -1076,7 +1126,7 @@ impl CheckCode {
|
||||
CheckCode::ANN102 => CheckKind::MissingTypeCls("...".to_string()),
|
||||
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction("...".to_string()),
|
||||
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction("...".to_string()),
|
||||
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod("...".to_string()),
|
||||
CheckCode::ANN204 => CheckKind::MissingReturnTypeSpecialMethod("...".to_string()),
|
||||
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
|
||||
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
|
||||
CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()),
|
||||
@@ -1113,6 +1163,7 @@ impl CheckCode {
|
||||
CheckCode::UP013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()),
|
||||
CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
|
||||
CheckCode::UP015 => CheckKind::RedundantOpenModes,
|
||||
CheckCode::UP016 => CheckKind::RemoveSixCompat,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1219,6 +1270,23 @@ impl CheckCode {
|
||||
CheckCode::ICN001 => {
|
||||
CheckKind::ImportAliasIsNotConventional("...".to_string(), "...".to_string())
|
||||
}
|
||||
// pandas-vet
|
||||
CheckCode::PDV002 => CheckKind::UseOfInplaceArgument,
|
||||
CheckCode::PDV003 => CheckKind::UseOfDotIsNull,
|
||||
CheckCode::PDV004 => CheckKind::UseOfDotNotNull,
|
||||
CheckCode::PDV007 => CheckKind::UseOfDotIx,
|
||||
CheckCode::PDV008 => CheckKind::UseOfDotAt,
|
||||
CheckCode::PDV009 => CheckKind::UseOfDotIat,
|
||||
CheckCode::PDV010 => CheckKind::UseOfDotPivotOrUnstack,
|
||||
CheckCode::PDV011 => CheckKind::UseOfDotValues,
|
||||
CheckCode::PDV012 => CheckKind::UseOfDotReadTable,
|
||||
CheckCode::PDV013 => CheckKind::UseOfDotStack,
|
||||
CheckCode::PDV015 => CheckKind::UseOfPdMerge,
|
||||
CheckCode::PDV901 => CheckKind::DfIsABadVariableName,
|
||||
// flake8-errmsg
|
||||
CheckCode::EM101 => CheckKind::RawStringInException,
|
||||
CheckCode::EM102 => CheckKind::FStringInException,
|
||||
CheckCode::EM103 => CheckKind::DotFormatInException,
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
@@ -1354,6 +1422,9 @@ impl CheckCode {
|
||||
CheckCode::E743 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E902 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E999 => CheckCategory::Pycodestyle,
|
||||
CheckCode::EM101 => CheckCategory::Flake8ErrMsg,
|
||||
CheckCode::EM102 => CheckCategory::Flake8ErrMsg,
|
||||
CheckCode::EM103 => CheckCategory::Flake8ErrMsg,
|
||||
CheckCode::ERA001 => CheckCategory::Eradicate,
|
||||
CheckCode::F401 => CheckCategory::Pyflakes,
|
||||
CheckCode::F402 => CheckCategory::Pyflakes,
|
||||
@@ -1420,6 +1491,18 @@ impl CheckCode {
|
||||
CheckCode::N816 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::PDV002 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV003 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV004 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV007 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV008 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV009 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV010 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV011 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV012 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV013 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV015 => CheckCategory::PandasVet,
|
||||
CheckCode::PDV901 => CheckCategory::PandasVet,
|
||||
CheckCode::PGH001 => CheckCategory::PygrepHooks,
|
||||
CheckCode::PLC0414 => CheckCategory::Pylint,
|
||||
CheckCode::PLC2201 => CheckCategory::Pylint,
|
||||
@@ -1473,6 +1556,7 @@ impl CheckCode {
|
||||
CheckCode::UP013 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP014 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP015 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP016 => CheckCategory::Pyupgrade,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::YTT101 => CheckCategory::Flake82020,
|
||||
@@ -1649,7 +1733,7 @@ impl CheckKind {
|
||||
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
|
||||
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
|
||||
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
|
||||
CheckKind::MissingReturnTypeMagicMethod(_) => &CheckCode::ANN204,
|
||||
CheckKind::MissingReturnTypeSpecialMethod(_) => &CheckCode::ANN204,
|
||||
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
|
||||
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
|
||||
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
|
||||
@@ -1681,6 +1765,7 @@ impl CheckKind {
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(_) => &CheckCode::UP013,
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::UP014,
|
||||
CheckKind::RedundantOpenModes => &CheckCode::UP015,
|
||||
CheckKind::RemoveSixCompat => &CheckCode::UP016,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
@@ -1770,6 +1855,23 @@ impl CheckKind {
|
||||
CheckKind::UnusedLambdaArgument(..) => &CheckCode::ARG005,
|
||||
// flake8-import-conventions
|
||||
CheckKind::ImportAliasIsNotConventional(..) => &CheckCode::ICN001,
|
||||
// pandas-vet
|
||||
CheckKind::UseOfInplaceArgument => &CheckCode::PDV002,
|
||||
CheckKind::UseOfDotIsNull => &CheckCode::PDV003,
|
||||
CheckKind::UseOfDotNotNull => &CheckCode::PDV004,
|
||||
CheckKind::UseOfDotIx => &CheckCode::PDV007,
|
||||
CheckKind::UseOfDotAt => &CheckCode::PDV008,
|
||||
CheckKind::UseOfDotIat => &CheckCode::PDV009,
|
||||
CheckKind::UseOfDotPivotOrUnstack => &CheckCode::PDV010,
|
||||
CheckKind::UseOfDotValues => &CheckCode::PDV011,
|
||||
CheckKind::UseOfDotReadTable => &CheckCode::PDV012,
|
||||
CheckKind::UseOfDotStack => &CheckCode::PDV013,
|
||||
CheckKind::UseOfPdMerge => &CheckCode::PDV015,
|
||||
CheckKind::DfIsABadVariableName => &CheckCode::PDV901,
|
||||
// flake8-errmsg
|
||||
CheckKind::RawStringInException => &CheckCode::EM101,
|
||||
CheckKind::FStringInException => &CheckCode::EM102,
|
||||
CheckKind::DotFormatInException => &CheckCode::EM103,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
@@ -2288,8 +2390,8 @@ impl CheckKind {
|
||||
CheckKind::MissingReturnTypePrivateFunction(name) => {
|
||||
format!("Missing return type annotation for private function `{name}`")
|
||||
}
|
||||
CheckKind::MissingReturnTypeMagicMethod(name) => {
|
||||
format!("Missing return type annotation for magic method `{name}`")
|
||||
CheckKind::MissingReturnTypeSpecialMethod(name) => {
|
||||
format!("Missing return type annotation for special method `{name}`")
|
||||
}
|
||||
CheckKind::MissingReturnTypeStaticMethod(name) => {
|
||||
format!("Missing return type annotation for staticmethod `{name}`")
|
||||
@@ -2372,6 +2474,7 @@ impl CheckKind {
|
||||
}
|
||||
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
|
||||
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
|
||||
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
@@ -2597,6 +2700,51 @@ impl CheckKind {
|
||||
CheckKind::ImportAliasIsNotConventional(name, asname) => {
|
||||
format!("`{name}` should be imported as `{asname}`")
|
||||
}
|
||||
// pandas-vet
|
||||
CheckKind::UseOfInplaceArgument => {
|
||||
"`inplace=True` should be avoided; it has inconsistent behavior".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotIsNull => {
|
||||
"`.isna` is preferred to `.isnull`; functionality is equivalent".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotNotNull => {
|
||||
"`.notna` is preferred to `.notnull`; functionality is equivalent".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotIx => {
|
||||
"``ix` i` deprecated; use more explicit `.loc` o` `.iloc`".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotAt => {
|
||||
"Use `.loc` instead of `.at`. If speed is important, use numpy.".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotIat => {
|
||||
"Use `.iloc` instea` of `.iat`. If speed is important, use numpy.".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotPivotOrUnstack => "`.pivot_table` is preferred to `.pivot` or \
|
||||
`.unstack`; provides same functionality"
|
||||
.to_string(),
|
||||
CheckKind::UseOfDotValues => "Use `.to_numpy()` instead of `.values`".to_string(),
|
||||
CheckKind::UseOfDotReadTable => {
|
||||
"`.read_csv` is preferred to `.read_table`; provides same functionality".to_string()
|
||||
}
|
||||
CheckKind::UseOfDotStack => {
|
||||
"`.melt` is preferred to `.stack`; provides same functionality".to_string()
|
||||
}
|
||||
CheckKind::DfIsABadVariableName => {
|
||||
"`df` is a bad variable name. Be kinder to your future self.".to_string()
|
||||
}
|
||||
CheckKind::UseOfPdMerge => "Use `.merge` method instead of `pd.merge` function. They \
|
||||
have equivalent functionality."
|
||||
.to_string(),
|
||||
// flake8-errmsg
|
||||
CheckKind::RawStringInException => {
|
||||
"Exception must not use a string literal, assign to variable first".to_string()
|
||||
}
|
||||
CheckKind::FStringInException => {
|
||||
"Exception must not use an f-string literal, assign to variable first".to_string()
|
||||
}
|
||||
CheckKind::DotFormatInException => "Exception must not use a `.format()` string \
|
||||
directly, assign to variable first"
|
||||
.to_string(),
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
@@ -2682,6 +2830,7 @@ impl CheckKind {
|
||||
| CheckKind::IsLiteral
|
||||
| CheckKind::KeyInDict(..)
|
||||
| CheckKind::MisplacedComparisonConstant(..)
|
||||
| CheckKind::MissingReturnTypeSpecialMethod(..)
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::NewLineAfterSectionName(..)
|
||||
| CheckKind::NoBlankLineAfterFunction(..)
|
||||
@@ -2696,13 +2845,14 @@ impl CheckKind {
|
||||
| CheckKind::NotIsTest
|
||||
| CheckKind::OneBlankLineAfterClass(..)
|
||||
| CheckKind::OneBlankLineBeforeClass(..)
|
||||
| CheckKind::PercentFormatExtraNamedArguments(..)
|
||||
| CheckKind::PEP3120UnnecessaryCodingComment
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PercentFormatExtraNamedArguments(..)
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::RaiseNotImplemented
|
||||
| CheckKind::RedundantOpenModes
|
||||
| CheckKind::RedundantTupleInExceptionHandler(..)
|
||||
| CheckKind::RemoveSixCompat
|
||||
| CheckKind::SectionNameEndsInColon(..)
|
||||
| CheckKind::SectionNotOverIndented(..)
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
@@ -2785,6 +2935,7 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(
|
||||
("U013", CheckCode::UP013),
|
||||
("U014", CheckCode::UP014),
|
||||
("U015", CheckCode::UP015),
|
||||
("U016", CheckCode::UP016),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", CheckCode::TID252),
|
||||
("M001", CheckCode::RUF100),
|
||||
|
||||
@@ -189,6 +189,12 @@ pub enum CheckCodePrefix {
|
||||
E902,
|
||||
E99,
|
||||
E999,
|
||||
EM,
|
||||
EM1,
|
||||
EM10,
|
||||
EM101,
|
||||
EM102,
|
||||
EM103,
|
||||
ERA,
|
||||
ERA0,
|
||||
ERA00,
|
||||
@@ -297,6 +303,24 @@ pub enum CheckCodePrefix {
|
||||
N816,
|
||||
N817,
|
||||
N818,
|
||||
PDV,
|
||||
PDV0,
|
||||
PDV00,
|
||||
PDV002,
|
||||
PDV003,
|
||||
PDV004,
|
||||
PDV007,
|
||||
PDV008,
|
||||
PDV009,
|
||||
PDV01,
|
||||
PDV010,
|
||||
PDV011,
|
||||
PDV012,
|
||||
PDV013,
|
||||
PDV015,
|
||||
PDV9,
|
||||
PDV90,
|
||||
PDV901,
|
||||
PGH,
|
||||
PGH0,
|
||||
PGH00,
|
||||
@@ -416,6 +440,7 @@ pub enum CheckCodePrefix {
|
||||
U013,
|
||||
U014,
|
||||
U015,
|
||||
U016,
|
||||
UP,
|
||||
UP0,
|
||||
UP00,
|
||||
@@ -434,6 +459,7 @@ pub enum CheckCodePrefix {
|
||||
UP013,
|
||||
UP014,
|
||||
UP015,
|
||||
UP016,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -1003,6 +1029,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::E902 => vec![CheckCode::E902],
|
||||
CheckCodePrefix::E99 => vec![CheckCode::E999],
|
||||
CheckCodePrefix::E999 => vec![CheckCode::E999],
|
||||
CheckCodePrefix::EM => vec![CheckCode::EM101, CheckCode::EM102, CheckCode::EM103],
|
||||
CheckCodePrefix::EM1 => vec![CheckCode::EM101, CheckCode::EM102, CheckCode::EM103],
|
||||
CheckCodePrefix::EM10 => vec![CheckCode::EM101, CheckCode::EM102, CheckCode::EM103],
|
||||
CheckCodePrefix::EM101 => vec![CheckCode::EM101],
|
||||
CheckCodePrefix::EM102 => vec![CheckCode::EM102],
|
||||
CheckCodePrefix::EM103 => vec![CheckCode::EM103],
|
||||
CheckCodePrefix::ERA => vec![CheckCode::ERA001],
|
||||
CheckCodePrefix::ERA0 => vec![CheckCode::ERA001],
|
||||
CheckCodePrefix::ERA00 => vec![CheckCode::ERA001],
|
||||
@@ -1336,6 +1368,62 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::N816 => vec![CheckCode::N816],
|
||||
CheckCodePrefix::N817 => vec![CheckCode::N817],
|
||||
CheckCodePrefix::N818 => vec![CheckCode::N818],
|
||||
CheckCodePrefix::PDV => vec![
|
||||
CheckCode::PDV002,
|
||||
CheckCode::PDV003,
|
||||
CheckCode::PDV004,
|
||||
CheckCode::PDV007,
|
||||
CheckCode::PDV008,
|
||||
CheckCode::PDV009,
|
||||
CheckCode::PDV010,
|
||||
CheckCode::PDV011,
|
||||
CheckCode::PDV012,
|
||||
CheckCode::PDV013,
|
||||
CheckCode::PDV015,
|
||||
CheckCode::PDV901,
|
||||
],
|
||||
CheckCodePrefix::PDV0 => vec![
|
||||
CheckCode::PDV002,
|
||||
CheckCode::PDV003,
|
||||
CheckCode::PDV004,
|
||||
CheckCode::PDV007,
|
||||
CheckCode::PDV008,
|
||||
CheckCode::PDV009,
|
||||
CheckCode::PDV010,
|
||||
CheckCode::PDV011,
|
||||
CheckCode::PDV012,
|
||||
CheckCode::PDV013,
|
||||
CheckCode::PDV015,
|
||||
],
|
||||
CheckCodePrefix::PDV00 => vec![
|
||||
CheckCode::PDV002,
|
||||
CheckCode::PDV003,
|
||||
CheckCode::PDV004,
|
||||
CheckCode::PDV007,
|
||||
CheckCode::PDV008,
|
||||
CheckCode::PDV009,
|
||||
],
|
||||
CheckCodePrefix::PDV002 => vec![CheckCode::PDV002],
|
||||
CheckCodePrefix::PDV003 => vec![CheckCode::PDV003],
|
||||
CheckCodePrefix::PDV004 => vec![CheckCode::PDV004],
|
||||
CheckCodePrefix::PDV007 => vec![CheckCode::PDV007],
|
||||
CheckCodePrefix::PDV008 => vec![CheckCode::PDV008],
|
||||
CheckCodePrefix::PDV009 => vec![CheckCode::PDV009],
|
||||
CheckCodePrefix::PDV01 => vec![
|
||||
CheckCode::PDV010,
|
||||
CheckCode::PDV011,
|
||||
CheckCode::PDV012,
|
||||
CheckCode::PDV013,
|
||||
CheckCode::PDV015,
|
||||
],
|
||||
CheckCodePrefix::PDV010 => vec![CheckCode::PDV010],
|
||||
CheckCodePrefix::PDV011 => vec![CheckCode::PDV011],
|
||||
CheckCodePrefix::PDV012 => vec![CheckCode::PDV012],
|
||||
CheckCodePrefix::PDV013 => vec![CheckCode::PDV013],
|
||||
CheckCodePrefix::PDV015 => vec![CheckCode::PDV015],
|
||||
CheckCodePrefix::PDV9 => vec![CheckCode::PDV901],
|
||||
CheckCodePrefix::PDV90 => vec![CheckCode::PDV901],
|
||||
CheckCodePrefix::PDV901 => vec![CheckCode::PDV901],
|
||||
CheckCodePrefix::PGH => vec![CheckCode::PGH001],
|
||||
CheckCodePrefix::PGH0 => vec![CheckCode::PGH001],
|
||||
CheckCodePrefix::PGH00 => vec![CheckCode::PGH001],
|
||||
@@ -1536,6 +1624,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
@@ -1560,6 +1649,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
@@ -1666,6 +1756,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U010 => {
|
||||
@@ -1722,6 +1813,15 @@ impl CheckCodePrefix {
|
||||
);
|
||||
vec![CheckCode::UP015]
|
||||
}
|
||||
CheckCodePrefix::U016 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U016` has been remapped to `UP016`".bold()
|
||||
);
|
||||
vec![CheckCode::UP016]
|
||||
}
|
||||
CheckCodePrefix::UP => vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
@@ -1737,6 +1837,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -1753,6 +1854,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -1779,6 +1881,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
],
|
||||
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
|
||||
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
|
||||
@@ -1786,6 +1889,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP013 => vec![CheckCode::UP013],
|
||||
CheckCodePrefix::UP014 => vec![CheckCode::UP014],
|
||||
CheckCodePrefix::UP015 => vec![CheckCode::UP015],
|
||||
CheckCodePrefix::UP016 => vec![CheckCode::UP016],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -2018,6 +2122,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::E902 => SuffixLength::Three,
|
||||
CheckCodePrefix::E99 => SuffixLength::Two,
|
||||
CheckCodePrefix::E999 => SuffixLength::Three,
|
||||
CheckCodePrefix::EM => SuffixLength::Zero,
|
||||
CheckCodePrefix::EM1 => SuffixLength::One,
|
||||
CheckCodePrefix::EM10 => SuffixLength::Two,
|
||||
CheckCodePrefix::EM101 => SuffixLength::Three,
|
||||
CheckCodePrefix::EM102 => SuffixLength::Three,
|
||||
CheckCodePrefix::EM103 => SuffixLength::Three,
|
||||
CheckCodePrefix::ERA => SuffixLength::Zero,
|
||||
CheckCodePrefix::ERA0 => SuffixLength::One,
|
||||
CheckCodePrefix::ERA00 => SuffixLength::Two,
|
||||
@@ -2126,6 +2236,24 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::N816 => SuffixLength::Three,
|
||||
CheckCodePrefix::N817 => SuffixLength::Three,
|
||||
CheckCodePrefix::N818 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV => SuffixLength::Zero,
|
||||
CheckCodePrefix::PDV0 => SuffixLength::One,
|
||||
CheckCodePrefix::PDV00 => SuffixLength::Two,
|
||||
CheckCodePrefix::PDV002 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV003 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV004 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV007 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV008 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV009 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV01 => SuffixLength::Two,
|
||||
CheckCodePrefix::PDV010 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV011 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV012 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV013 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV015 => SuffixLength::Three,
|
||||
CheckCodePrefix::PDV9 => SuffixLength::One,
|
||||
CheckCodePrefix::PDV90 => SuffixLength::Two,
|
||||
CheckCodePrefix::PDV901 => SuffixLength::Three,
|
||||
CheckCodePrefix::PGH => SuffixLength::Zero,
|
||||
CheckCodePrefix::PGH0 => SuffixLength::One,
|
||||
CheckCodePrefix::PGH00 => SuffixLength::Two,
|
||||
@@ -2245,6 +2373,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U013 => SuffixLength::Three,
|
||||
CheckCodePrefix::U014 => SuffixLength::Three,
|
||||
CheckCodePrefix::U015 => SuffixLength::Three,
|
||||
CheckCodePrefix::U016 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP => SuffixLength::Zero,
|
||||
CheckCodePrefix::UP0 => SuffixLength::One,
|
||||
CheckCodePrefix::UP00 => SuffixLength::Two,
|
||||
@@ -2263,6 +2392,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP013 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP014 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP015 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP016 => SuffixLength::Three,
|
||||
CheckCodePrefix::W => SuffixLength::Zero,
|
||||
CheckCodePrefix::W2 => SuffixLength::One,
|
||||
CheckCodePrefix::W29 => SuffixLength::Two,
|
||||
@@ -2300,12 +2430,14 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::C,
|
||||
CheckCodePrefix::D,
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::EM,
|
||||
CheckCodePrefix::ERA,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::FBT,
|
||||
CheckCodePrefix::I,
|
||||
CheckCodePrefix::ICN,
|
||||
CheckCodePrefix::N,
|
||||
CheckCodePrefix::PDV,
|
||||
CheckCodePrefix::PGH,
|
||||
CheckCodePrefix::PLC,
|
||||
CheckCodePrefix::PLE,
|
||||
|
||||
13
src/cli.rs
13
src/cli.rs
@@ -86,10 +86,16 @@ pub struct Cli {
|
||||
show_source: bool,
|
||||
#[clap(long, overrides_with("show_source"), hide = true)]
|
||||
no_show_source: bool,
|
||||
/// Respect file exclusions via `.gitignore` and other standard ignore
|
||||
/// files.
|
||||
#[arg(long, overrides_with("no_respect_gitignore"))]
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
no_respect_gitignore: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
/// See the settings Ruff used for the first matching file.
|
||||
/// See the settings Ruff will use to check a given Python file.
|
||||
#[arg(long)]
|
||||
pub show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
@@ -156,6 +162,10 @@ impl Cli {
|
||||
line_length: self.line_length,
|
||||
max_complexity: self.max_complexity,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
select: self.select,
|
||||
show_source: resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
target_version: self.target_version,
|
||||
@@ -212,6 +222,7 @@ pub struct Overrides {
|
||||
pub line_length: Option<usize>,
|
||||
pub max_complexity: Option<usize>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub respect_gitignore: Option<bool>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
pub show_source: Option<bool>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use ignore::Error;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
@@ -17,20 +18,22 @@ use crate::iterators::par_iter;
|
||||
use crate::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
|
||||
use crate::message::Message;
|
||||
use crate::resolver;
|
||||
use crate::resolver::Strategy;
|
||||
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
|
||||
use crate::settings::types::SerializationFormat;
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
pub fn run(
|
||||
files: &[PathBuf],
|
||||
strategy: &Strategy,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
cache: bool,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = resolver::resolve_python_files(files, strategy, overrides)?;
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
@@ -40,19 +43,23 @@ pub fn run(
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, strategy);
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
lint_path(path, settings, &cache.into(), autofix)
|
||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
||||
}
|
||||
Err(e) => Err((
|
||||
e.path().map(Path::to_owned),
|
||||
if let Error::WithPath { path, .. } = e {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
}
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, strategy);
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
if settings.enabled.contains(&CheckCode::E902) {
|
||||
Diagnostics::new(vec![Message {
|
||||
kind: CheckKind::IOError(message),
|
||||
@@ -93,14 +100,14 @@ fn read_from_stdin() -> Result<String> {
|
||||
|
||||
/// Run the linter over a single file, read from `stdin`.
|
||||
pub fn run_stdin(
|
||||
strategy: &Strategy,
|
||||
strategy: &PyprojectDiscovery,
|
||||
filename: &Path,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
let stdin = read_from_stdin()?;
|
||||
let settings = match strategy {
|
||||
Strategy::Fixed(settings) => settings,
|
||||
Strategy::Hierarchical(settings) => settings,
|
||||
PyprojectDiscovery::Fixed(settings) => settings,
|
||||
PyprojectDiscovery::Hierarchical(settings) => settings,
|
||||
};
|
||||
let mut diagnostics = lint_stdin(filename, &stdin, settings, autofix)?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
@@ -108,10 +115,16 @@ pub fn run_stdin(
|
||||
}
|
||||
|
||||
/// Add `noqa` directives to a collection of files.
|
||||
pub fn add_noqa(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -> Result<usize> {
|
||||
pub fn add_noqa(
|
||||
files: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = resolver::resolve_python_files(files, strategy, overrides)?;
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
@@ -120,7 +133,7 @@ pub fn add_noqa(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, strategy);
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
match add_noqa_to_path(path, settings) {
|
||||
Ok(count) => Some(count),
|
||||
Err(e) => {
|
||||
@@ -138,10 +151,16 @@ pub fn add_noqa(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -
|
||||
}
|
||||
|
||||
/// Automatically format a collection of files.
|
||||
pub fn autoformat(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -> Result<usize> {
|
||||
pub fn autoformat(
|
||||
files: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to format.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = resolver::resolve_python_files(files, strategy, overrides)?;
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
@@ -150,7 +169,7 @@ pub fn autoformat(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides)
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, strategy);
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
match autoformat_path(path, settings) {
|
||||
Ok(()) => Some(()),
|
||||
Err(e) => {
|
||||
@@ -168,9 +187,15 @@ pub fn autoformat(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides)
|
||||
}
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub fn show_settings(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -> Result<()> {
|
||||
pub fn show_settings(
|
||||
files: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, resolver) = resolver::resolve_python_files(files, strategy, overrides)?;
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
|
||||
// Print the list of files.
|
||||
let Some(entry) = paths
|
||||
@@ -180,7 +205,7 @@ pub fn show_settings(files: &[PathBuf], strategy: &Strategy, overrides: &Overrid
|
||||
bail!("No files found under the given path");
|
||||
};
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, strategy);
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
println!("Resolved settings for: {path:?}");
|
||||
println!("{settings:#?}");
|
||||
|
||||
@@ -188,9 +213,15 @@ pub fn show_settings(files: &[PathBuf], strategy: &Strategy, overrides: &Overrid
|
||||
}
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub fn show_files(files: &[PathBuf], strategy: &Strategy, overrides: &Overrides) -> Result<()> {
|
||||
pub fn show_files(
|
||||
files: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, _resolver) = resolver::resolve_python_files(files, strategy, overrides)?;
|
||||
let (paths, _resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
|
||||
// Print the list of files.
|
||||
for entry in paths
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
use crate::eradicate::detection::comment_contains_code;
|
||||
use crate::settings::flags;
|
||||
use crate::{Check, Settings, SourceCodeLocator};
|
||||
|
||||
fn is_standalone_comment(line: &str) -> bool {
|
||||
@@ -23,7 +24,7 @@ pub fn commented_out_code(
|
||||
start: Location,
|
||||
end: Location,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Check> {
|
||||
let location = Location::new(start.row(), 0);
|
||||
let end_location = Location::new(end.row() + 1, 0);
|
||||
@@ -41,7 +42,9 @@ pub fn commented_out_code(
|
||||
end_location: end,
|
||||
},
|
||||
);
|
||||
if autofix && settings.fixable.contains(&CheckCode::ERA001) {
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::ERA001)
|
||||
{
|
||||
check.amend(Fix::deletion(location, end_location));
|
||||
}
|
||||
Some(check)
|
||||
|
||||
@@ -21,7 +21,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -29,7 +29,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
45
src/flake8_annotations/fixes.rs
Normal file
45
src/flake8_annotations/fixes.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use anyhow::{bail, Result};
|
||||
use rustpython_ast::Stmt;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// ANN204
|
||||
pub fn add_return_none_annotation(locator: &SourceCodeLocator, stmt: &Stmt) -> Result<Fix> {
|
||||
let range = Range::from_located(stmt);
|
||||
let contents = locator.slice_source_code_range(&range);
|
||||
|
||||
// Find the colon (following the `def` keyword).
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, ..) in lexer::make_tokenizer(&contents).flatten() {
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
return Ok(Fix::insertion(
|
||||
" -> None".to_string(),
|
||||
helpers::to_absolute(start, range.location),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
seen_lpar = true;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
seen_rpar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Unable to locate colon in function definition");
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod fixes;
|
||||
pub mod helpers;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
@@ -31,7 +32,6 @@ mod tests {
|
||||
CheckCode::ANN401,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -57,7 +57,6 @@ mod tests {
|
||||
CheckCode::ANN102,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -83,7 +82,6 @@ mod tests {
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -109,7 +107,6 @@ mod tests {
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -129,7 +126,6 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::ANN401])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -149,7 +145,6 @@ mod tests {
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -6,6 +7,7 @@ use crate::ast::{cast, visitor};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::flake8_annotations::fixes;
|
||||
use crate::flake8_annotations::helpers::match_function_def;
|
||||
use crate::visibility::Visibility;
|
||||
use crate::{visibility, Check};
|
||||
@@ -295,8 +297,8 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
}
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
// only returns `None` (explicitly or implicitly).
|
||||
// Allow omission of return annotation if the function only returns `None`
|
||||
// (explicitly or implicitly).
|
||||
if checker.settings.flake8_annotations.suppress_none_returning
|
||||
&& is_none_returning(body)
|
||||
{
|
||||
@@ -317,13 +319,6 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
} else if visibility::is_magic(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN204) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
} else if visibility::is_init(stmt) {
|
||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||
// least one argument is typed.
|
||||
@@ -331,12 +326,26 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !(checker.settings.flake8_annotations.mypy_init_return
|
||||
&& has_any_typed_arg)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
|
||||
let mut check = Check::new(
|
||||
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match fixes::add_return_none_annotation(checker.locator, stmt) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {e}"),
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
} else if visibility::is_magic(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN204) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
match visibility {
|
||||
Visibility::Public => {
|
||||
|
||||
@@ -8,7 +8,7 @@ expression: checks
|
||||
row: 29
|
||||
column: 4
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
row: 30
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 0
|
||||
row: 5
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
MissingTypeFunctionArgument: a
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 0
|
||||
row: 10
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
MissingTypeFunctionArgument: b
|
||||
@@ -62,8 +62,8 @@ expression: checks
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 0
|
||||
row: 20
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
MissingReturnTypePublicFunction: foo
|
||||
@@ -71,8 +71,8 @@ expression: checks
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 29
|
||||
column: 0
|
||||
row: 25
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression: a
|
||||
|
||||
@@ -3,30 +3,44 @@ source: src/flake8_annotations/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
MissingReturnTypeMagicMethod: __init__
|
||||
MissingReturnTypeSpecialMethod: __init__
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
fix: ~
|
||||
row: 6
|
||||
column: 11
|
||||
fix:
|
||||
content: " -> None"
|
||||
location:
|
||||
row: 5
|
||||
column: 22
|
||||
end_location:
|
||||
row: 5
|
||||
column: 22
|
||||
- kind:
|
||||
MissingReturnTypeMagicMethod: __init__
|
||||
MissingReturnTypeSpecialMethod: __init__
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
fix: ~
|
||||
row: 12
|
||||
column: 11
|
||||
fix:
|
||||
content: " -> None"
|
||||
location:
|
||||
row: 11
|
||||
column: 27
|
||||
end_location:
|
||||
row: 11
|
||||
column: 27
|
||||
- kind:
|
||||
MissingReturnTypePrivateFunction: __init__
|
||||
location:
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 42
|
||||
column: 0
|
||||
row: 41
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 45
|
||||
column: 0
|
||||
end_location:
|
||||
row: 50
|
||||
column: 0
|
||||
row: 46
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
MissingReturnTypePublicFunction: foo
|
||||
@@ -17,7 +17,7 @@ expression: checks
|
||||
row: 50
|
||||
column: 0
|
||||
end_location:
|
||||
row: 56
|
||||
column: 0
|
||||
row: 55
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -20,7 +20,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -22,7 +22,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -47,7 +47,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -68,7 +67,6 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::B008])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -56,16 +56,23 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
if KWLIST.contains(&name.as_str()) {
|
||||
return;
|
||||
}
|
||||
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
match assignment(obj, name, value) {
|
||||
Ok(content) => check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
)),
|
||||
Err(e) => error!("Failed to fix invalid comparison: {e}"),
|
||||
};
|
||||
// We can only replace a `setattr` call (which is an `Expr`) with an assignment
|
||||
// (which is a `Stmt`) if the `Expr` is already being used as a `Stmt`
|
||||
// (i.e., it's directly within an `StmtKind::Expr`).
|
||||
if let StmtKind::Expr { value: child } = &checker.current_parent().0.node {
|
||||
if expr == child.as_ref() {
|
||||
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
match assignment(obj, name, value) {
|
||||
Ok(content) => check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
)),
|
||||
Err(e) => error!("Failed to fix invalid comparison: {e}"),
|
||||
};
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -77,4 +77,19 @@ expression: checks
|
||||
end_location:
|
||||
row: 22
|
||||
column: 31
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 23
|
||||
column: 3
|
||||
end_location:
|
||||
row: 23
|
||||
column: 20
|
||||
fix:
|
||||
content: x.bar
|
||||
location:
|
||||
row: 23
|
||||
column: 3
|
||||
end_location:
|
||||
row: 23
|
||||
column: 20
|
||||
|
||||
|
||||
@@ -4,77 +4,77 @@ expression: checks
|
||||
---
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 33
|
||||
row: 37
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
row: 37
|
||||
column: 25
|
||||
fix:
|
||||
content: foo.bar = None
|
||||
location:
|
||||
row: 33
|
||||
row: 37
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
row: 37
|
||||
column: 25
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 34
|
||||
row: 38
|
||||
column: 0
|
||||
end_location:
|
||||
row: 34
|
||||
row: 38
|
||||
column: 29
|
||||
fix:
|
||||
content: foo._123abc = None
|
||||
location:
|
||||
row: 34
|
||||
row: 38
|
||||
column: 0
|
||||
end_location:
|
||||
row: 34
|
||||
row: 38
|
||||
column: 29
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 35
|
||||
row: 39
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
row: 39
|
||||
column: 28
|
||||
fix:
|
||||
content: foo.abc123 = None
|
||||
location:
|
||||
row: 35
|
||||
row: 39
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
row: 39
|
||||
column: 28
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 36
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
row: 40
|
||||
column: 29
|
||||
fix:
|
||||
content: foo.abc123 = None
|
||||
location:
|
||||
row: 36
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
row: 40
|
||||
column: 29
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 37
|
||||
row: 41
|
||||
column: 0
|
||||
end_location:
|
||||
row: 37
|
||||
row: 41
|
||||
column: 30
|
||||
fix:
|
||||
content: foo.bar.baz = None
|
||||
location:
|
||||
row: 37
|
||||
row: 41
|
||||
column: 0
|
||||
end_location:
|
||||
row: 37
|
||||
row: 41
|
||||
column: 30
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ expression: checks
|
||||
row: 22
|
||||
column: 8
|
||||
end_location:
|
||||
row: 25
|
||||
column: 4
|
||||
row: 23
|
||||
column: 42
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 22
|
||||
column: 0
|
||||
row: 19
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: MetaBase_1
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 58
|
||||
column: 0
|
||||
end_location:
|
||||
row: 63
|
||||
column: 0
|
||||
row: 60
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_Base_1
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 69
|
||||
column: 0
|
||||
end_location:
|
||||
row: 74
|
||||
column: 0
|
||||
row: 71
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_Base_2
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 74
|
||||
column: 0
|
||||
end_location:
|
||||
row: 79
|
||||
column: 0
|
||||
row: 76
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: notabc_Base_1
|
||||
@@ -44,8 +44,8 @@ expression: checks
|
||||
row: 79
|
||||
column: 0
|
||||
end_location:
|
||||
row: 84
|
||||
column: 0
|
||||
row: 81
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_set_class_variable_4
|
||||
@@ -53,7 +53,7 @@ expression: checks
|
||||
row: 128
|
||||
column: 0
|
||||
end_location:
|
||||
row: 130
|
||||
column: 0
|
||||
row: 129
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 22
|
||||
column: 0
|
||||
row: 20
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: pickle.PickleError
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 22
|
||||
column: 0
|
||||
end_location:
|
||||
row: 31
|
||||
column: 0
|
||||
row: 29
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: TypeError
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 39
|
||||
column: 0
|
||||
row: 38
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: ValueError
|
||||
@@ -35,7 +35,7 @@ expression: checks
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 39
|
||||
column: 0
|
||||
row: 38
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 15
|
||||
column: 4
|
||||
row: 13
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 15
|
||||
column: 4
|
||||
end_location:
|
||||
row: 18
|
||||
column: 4
|
||||
row: 16
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 22
|
||||
column: 4
|
||||
row: 20
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
@@ -35,7 +35,7 @@ expression: checks
|
||||
row: 22
|
||||
column: 4
|
||||
end_location:
|
||||
row: 29
|
||||
column: 4
|
||||
row: 27
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
use rustpython_ast::Located;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_builtins::types::ShadowingType;
|
||||
use crate::python::builtins::BUILTINS;
|
||||
|
||||
/// Check builtin name shadowing.
|
||||
pub fn builtin_shadowing(name: &str, location: Range, node_type: ShadowingType) -> Option<Check> {
|
||||
pub fn builtin_shadowing<T>(
|
||||
name: &str,
|
||||
located: &Located<T>,
|
||||
node_type: ShadowingType,
|
||||
) -> Option<Check> {
|
||||
if BUILTINS.contains(&name) {
|
||||
Some(Check::new(
|
||||
match node_type {
|
||||
@@ -12,7 +18,7 @@ pub fn builtin_shadowing(name: &str, location: Range, node_type: ShadowingType)
|
||||
ShadowingType::Argument => CheckKind::BuiltinArgumentShadowing(name.to_string()),
|
||||
ShadowingType::Attribute => CheckKind::BuiltinAttributeShadowing(name.to_string()),
|
||||
},
|
||||
location,
|
||||
Range::from_located(located),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -23,7 +23,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -89,8 +89,8 @@ expression: checks
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 0
|
||||
row: 11
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: slice
|
||||
@@ -98,8 +98,8 @@ expression: checks
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
row: 14
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: ValueError
|
||||
@@ -107,8 +107,8 @@ expression: checks
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 0
|
||||
row: 19
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: memoryview
|
||||
|
||||
@@ -17,7 +17,7 @@ expression: checks
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 9
|
||||
column: 0
|
||||
row: 8
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
46
src/flake8_errmsg/checks.rs
Normal file
46
src/flake8_errmsg/checks.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn check_string_in_exception(exc: &Expr, max_string_length: usize) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
|
||||
if let ExprKind::Call { args, .. } = &exc.node {
|
||||
if let Some(first) = args.first() {
|
||||
match &first.node {
|
||||
// Check for string literals
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => {
|
||||
if string.len() > max_string_length {
|
||||
checks.push(Check::new(
|
||||
CheckKind::RawStringInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Check for f-strings
|
||||
ExprKind::JoinedStr { .. } => checks.push(Check::new(
|
||||
CheckKind::FStringInException,
|
||||
Range::from_located(first),
|
||||
)),
|
||||
// Check for .format() calls
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::DotFormatInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
48
src/flake8_errmsg/mod.rs
Normal file
48
src/flake8_errmsg/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
pub mod checks;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_errmsg, settings};
|
||||
|
||||
#[test]
|
||||
fn defaults() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_errmsg/EM.py"),
|
||||
&settings::Settings::for_rules(vec![
|
||||
CheckCode::EM101,
|
||||
CheckCode::EM102,
|
||||
CheckCode::EM103,
|
||||
]),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("defaults", checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_errmsg/EM.py"),
|
||||
&settings::Settings {
|
||||
flake8_errmsg: flake8_errmsg::settings::Settings {
|
||||
max_string_length: 20,
|
||||
},
|
||||
..settings::Settings::for_rules(vec![
|
||||
CheckCode::EM101,
|
||||
CheckCode::EM102,
|
||||
CheckCode::EM103,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("custom", checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
32
src/flake8_errmsg/settings.rs
Normal file
32
src/flake8_errmsg/settings.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Settings for the `flake8-errmsg` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Maximum string length for string literals in exception messages.
|
||||
"#,
|
||||
default = "0",
|
||||
value_type = "usize",
|
||||
example = "max-string-length = 20"
|
||||
)]
|
||||
pub max_string_length: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Settings {
|
||||
pub max_string_length: usize,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
max_string_length: options.max_string_length.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/flake8_errmsg/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: RawStringInException
|
||||
location:
|
||||
row: 5
|
||||
column: 23
|
||||
end_location:
|
||||
row: 5
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind: FStringInException
|
||||
location:
|
||||
row: 14
|
||||
column: 23
|
||||
end_location:
|
||||
row: 14
|
||||
column: 56
|
||||
fix: ~
|
||||
- kind: DotFormatInException
|
||||
location:
|
||||
row: 18
|
||||
column: 23
|
||||
end_location:
|
||||
row: 18
|
||||
column: 81
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/flake8_errmsg/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: RawStringInException
|
||||
location:
|
||||
row: 5
|
||||
column: 23
|
||||
end_location:
|
||||
row: 5
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind: RawStringInException
|
||||
location:
|
||||
row: 9
|
||||
column: 23
|
||||
end_location:
|
||||
row: 9
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind: FStringInException
|
||||
location:
|
||||
row: 14
|
||||
column: 23
|
||||
end_location:
|
||||
row: 14
|
||||
column: 56
|
||||
fix: ~
|
||||
- kind: DotFormatInException
|
||||
location:
|
||||
row: 18
|
||||
column: 23
|
||||
end_location:
|
||||
row: 18
|
||||
column: 81
|
||||
fix: ~
|
||||
|
||||
@@ -18,7 +18,6 @@ mod tests {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_import_conventions/defaults.py"),
|
||||
&Settings::for_rule(CheckCode::ICN001),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("defaults", checks);
|
||||
@@ -42,7 +41,6 @@ mod tests {
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("custom", checks);
|
||||
@@ -68,7 +66,6 @@ mod tests {
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("remove_default", checks);
|
||||
@@ -92,7 +89,6 @@ mod tests {
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("override_default", checks);
|
||||
|
||||
@@ -23,7 +23,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -23,7 +23,12 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
let defined_in = checker.current_grandparent();
|
||||
if matches!(defined_by.0.node, StmtKind::Expr { .. }) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(|node| node.0).collect();
|
||||
match helpers::remove_stmt(defined_by.0, defined_in.map(|node| node.0), &deleted) {
|
||||
match helpers::delete_stmt(
|
||||
defined_by.0,
|
||||
defined_in.map(|node| node.0),
|
||||
&deleted,
|
||||
checker.locator,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
checker.deletions.insert(defined_by.clone());
|
||||
|
||||
@@ -38,7 +38,6 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -70,7 +69,6 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -107,7 +105,6 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -144,7 +141,6 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -28,7 +28,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -7,8 +7,8 @@ expression: checks
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 12
|
||||
column: 0
|
||||
row: 8
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: ImplicitReturn
|
||||
location:
|
||||
@@ -45,8 +45,8 @@ expression: checks
|
||||
row: 29
|
||||
column: 8
|
||||
end_location:
|
||||
row: 34
|
||||
column: 0
|
||||
row: 30
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: ImplicitReturn
|
||||
location:
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
row: 13
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Elif
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 27
|
||||
column: 4
|
||||
row: 26
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Elif
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 38
|
||||
column: 4
|
||||
end_location:
|
||||
row: 49
|
||||
column: 0
|
||||
row: 46
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Else
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 50
|
||||
column: 4
|
||||
end_location:
|
||||
row: 58
|
||||
column: 0
|
||||
row: 55
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Else
|
||||
@@ -44,8 +44,8 @@ expression: checks
|
||||
row: 61
|
||||
column: 8
|
||||
end_location:
|
||||
row: 67
|
||||
column: 4
|
||||
row: 66
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Else
|
||||
@@ -53,8 +53,8 @@ expression: checks
|
||||
row: 73
|
||||
column: 4
|
||||
end_location:
|
||||
row: 81
|
||||
column: 4
|
||||
row: 80
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Else
|
||||
@@ -62,8 +62,8 @@ expression: checks
|
||||
row: 86
|
||||
column: 8
|
||||
end_location:
|
||||
row: 91
|
||||
column: 4
|
||||
row: 90
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseReturn: Else
|
||||
@@ -71,7 +71,7 @@ expression: checks
|
||||
row: 97
|
||||
column: 4
|
||||
end_location:
|
||||
row: 109
|
||||
column: 0
|
||||
row: 103
|
||||
column: 23
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
row: 13
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Elif
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 27
|
||||
column: 4
|
||||
row: 26
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Else
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 31
|
||||
column: 4
|
||||
end_location:
|
||||
row: 39
|
||||
column: 0
|
||||
row: 36
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Else
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 42
|
||||
column: 8
|
||||
end_location:
|
||||
row: 48
|
||||
column: 4
|
||||
row: 47
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Else
|
||||
@@ -44,8 +44,8 @@ expression: checks
|
||||
row: 54
|
||||
column: 4
|
||||
end_location:
|
||||
row: 62
|
||||
column: 4
|
||||
row: 61
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Else
|
||||
@@ -53,8 +53,8 @@ expression: checks
|
||||
row: 67
|
||||
column: 8
|
||||
end_location:
|
||||
row: 72
|
||||
column: 4
|
||||
row: 71
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseRaise: Else
|
||||
@@ -62,7 +62,7 @@ expression: checks
|
||||
row: 78
|
||||
column: 4
|
||||
end_location:
|
||||
row: 90
|
||||
column: 0
|
||||
row: 84
|
||||
column: 33
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 6
|
||||
column: 8
|
||||
end_location:
|
||||
row: 14
|
||||
column: 0
|
||||
row: 11
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Elif
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 16
|
||||
column: 8
|
||||
end_location:
|
||||
row: 26
|
||||
column: 8
|
||||
row: 25
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Else
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 34
|
||||
column: 8
|
||||
end_location:
|
||||
row: 40
|
||||
column: 0
|
||||
row: 37
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Else
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 44
|
||||
column: 12
|
||||
end_location:
|
||||
row: 50
|
||||
column: 8
|
||||
row: 49
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Else
|
||||
@@ -44,8 +44,8 @@ expression: checks
|
||||
row: 57
|
||||
column: 8
|
||||
end_location:
|
||||
row: 65
|
||||
column: 8
|
||||
row: 64
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Else
|
||||
@@ -53,8 +53,8 @@ expression: checks
|
||||
row: 71
|
||||
column: 12
|
||||
end_location:
|
||||
row: 76
|
||||
column: 8
|
||||
row: 75
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseContinue: Else
|
||||
@@ -62,7 +62,7 @@ expression: checks
|
||||
row: 83
|
||||
column: 8
|
||||
end_location:
|
||||
row: 92
|
||||
column: 0
|
||||
row: 89
|
||||
column: 24
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ expression: checks
|
||||
row: 6
|
||||
column: 8
|
||||
end_location:
|
||||
row: 14
|
||||
column: 0
|
||||
row: 11
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Elif
|
||||
@@ -17,8 +17,8 @@ expression: checks
|
||||
row: 16
|
||||
column: 8
|
||||
end_location:
|
||||
row: 26
|
||||
column: 8
|
||||
row: 25
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Else
|
||||
@@ -26,8 +26,8 @@ expression: checks
|
||||
row: 31
|
||||
column: 8
|
||||
end_location:
|
||||
row: 37
|
||||
column: 0
|
||||
row: 34
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Else
|
||||
@@ -35,8 +35,8 @@ expression: checks
|
||||
row: 41
|
||||
column: 12
|
||||
end_location:
|
||||
row: 47
|
||||
column: 8
|
||||
row: 46
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Else
|
||||
@@ -44,8 +44,8 @@ expression: checks
|
||||
row: 54
|
||||
column: 8
|
||||
end_location:
|
||||
row: 62
|
||||
column: 8
|
||||
row: 61
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Else
|
||||
@@ -53,8 +53,8 @@ expression: checks
|
||||
row: 68
|
||||
column: 12
|
||||
end_location:
|
||||
row: 73
|
||||
column: 8
|
||||
row: 72
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
SuperfluousElseBreak: Else
|
||||
@@ -62,7 +62,7 @@ expression: checks
|
||||
row: 80
|
||||
column: 8
|
||||
end_location:
|
||||
row: 92
|
||||
column: 0
|
||||
row: 86
|
||||
column: 21
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -22,7 +22,6 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::TID252])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -39,7 +38,6 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::TID252])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod helpers;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -12,7 +13,7 @@ mod tests {
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
use crate::{flake8_unused_arguments, settings};
|
||||
|
||||
#[test_case(CheckCode::ARG001, Path::new("ARG.py"); "ARG001")]
|
||||
#[test_case(CheckCode::ARG002, Path::new("ARG.py"); "ARG002")]
|
||||
@@ -26,10 +27,53 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_variadic_names() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py"),
|
||||
&settings::Settings {
|
||||
flake8_unused_arguments: flake8_unused_arguments::settings::Settings {
|
||||
ignore_variadic_names: true,
|
||||
},
|
||||
..settings::Settings::for_rules(vec![
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_variadic_names() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py"),
|
||||
&settings::Settings {
|
||||
flake8_unused_arguments: flake8_unused_arguments::settings::Settings {
|
||||
ignore_variadic_names: false,
|
||||
},
|
||||
..settings::Settings::for_rules(vec![
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use rustpython_ast::{Arg, Arguments};
|
||||
|
||||
use crate::ast::function_type;
|
||||
use crate::ast::function_type::FunctionType;
|
||||
use crate::ast::helpers::collect_arg_names;
|
||||
use crate::ast::types::{Binding, BindingKind, FunctionDef, Lambda, Scope, ScopeKind};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::flake8_unused_arguments::helpers;
|
||||
@@ -20,16 +19,35 @@ fn function(
|
||||
values: &FxHashMap<&str, usize>,
|
||||
bindings: &[Binding],
|
||||
dummy_variable_rgx: &Regex,
|
||||
ignore_variadic_names: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
for arg_name in collect_arg_names(args) {
|
||||
if let Some(binding) = values.get(arg_name).map(|index| &bindings[*index]) {
|
||||
for arg in args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.chain(
|
||||
iter::once::<Option<&Arg>>(args.vararg.as_deref())
|
||||
.flatten()
|
||||
.skip(usize::from(ignore_variadic_names)),
|
||||
)
|
||||
.chain(
|
||||
iter::once::<Option<&Arg>>(args.kwarg.as_deref())
|
||||
.flatten()
|
||||
.skip(usize::from(ignore_variadic_names)),
|
||||
)
|
||||
{
|
||||
if let Some(binding) = values
|
||||
.get(&arg.node.arg.as_str())
|
||||
.map(|index| &bindings[*index])
|
||||
{
|
||||
if binding.used.is_none()
|
||||
&& matches!(binding.kind, BindingKind::Argument)
|
||||
&& !dummy_variable_rgx.is_match(arg_name)
|
||||
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
||||
{
|
||||
checks.push(Check::new(
|
||||
argumentable.check_for(arg_name.to_string()),
|
||||
argumentable.check_for(arg.node.arg.to_string()),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
@@ -45,6 +63,7 @@ fn method(
|
||||
values: &FxHashMap<&str, usize>,
|
||||
bindings: &[Binding],
|
||||
dummy_variable_rgx: &Regex,
|
||||
ignore_variadic_names: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
for arg in args
|
||||
@@ -53,8 +72,16 @@ fn method(
|
||||
.chain(args.args.iter())
|
||||
.skip(1)
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.chain(iter::once::<Option<&Arg>>(args.vararg.as_deref()).flatten())
|
||||
.chain(iter::once::<Option<&Arg>>(args.kwarg.as_deref()).flatten())
|
||||
.chain(
|
||||
iter::once::<Option<&Arg>>(args.vararg.as_deref())
|
||||
.flatten()
|
||||
.skip(usize::from(ignore_variadic_names)),
|
||||
)
|
||||
.chain(
|
||||
iter::once::<Option<&Arg>>(args.kwarg.as_deref())
|
||||
.flatten()
|
||||
.skip(usize::from(ignore_variadic_names)),
|
||||
)
|
||||
{
|
||||
if let Some(binding) = values
|
||||
.get(&arg.node.arg.as_str())
|
||||
@@ -110,6 +137,10 @@ pub fn unused_arguments(
|
||||
&scope.values,
|
||||
bindings,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
checker
|
||||
.settings
|
||||
.flake8_unused_arguments
|
||||
.ignore_variadic_names,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
@@ -130,6 +161,10 @@ pub fn unused_arguments(
|
||||
&scope.values,
|
||||
bindings,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
checker
|
||||
.settings
|
||||
.flake8_unused_arguments
|
||||
.ignore_variadic_names,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
@@ -150,6 +185,10 @@ pub fn unused_arguments(
|
||||
&scope.values,
|
||||
bindings,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
checker
|
||||
.settings
|
||||
.flake8_unused_arguments
|
||||
.ignore_variadic_names,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
@@ -170,6 +209,10 @@ pub fn unused_arguments(
|
||||
&scope.values,
|
||||
bindings,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
checker
|
||||
.settings
|
||||
.flake8_unused_arguments
|
||||
.ignore_variadic_names,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
@@ -189,6 +232,10 @@ pub fn unused_arguments(
|
||||
&scope.values,
|
||||
bindings,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
checker
|
||||
.settings
|
||||
.flake8_unused_arguments
|
||||
.ignore_variadic_names,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
|
||||
32
src/flake8_unused_arguments/settings.rs
Normal file
32
src/flake8_unused_arguments/settings.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Settings for the `flake8-unused-arguments` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "ignore-variadic-names = true"
|
||||
)]
|
||||
pub ignore_variadic_names: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Default)]
|
||||
pub struct Settings {
|
||||
pub ignore_variadic_names: bool,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
ignore_variadic_names: options.ignore_variadic_names.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedFunctionArgument: a
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: b
|
||||
location:
|
||||
row: 1
|
||||
column: 9
|
||||
end_location:
|
||||
row: 1
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: a
|
||||
location:
|
||||
row: 5
|
||||
column: 6
|
||||
end_location:
|
||||
row: 5
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: b
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: args
|
||||
location:
|
||||
row: 5
|
||||
column: 13
|
||||
end_location:
|
||||
row: 5
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: kwargs
|
||||
location:
|
||||
row: 5
|
||||
column: 21
|
||||
end_location:
|
||||
row: 5
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: a
|
||||
location:
|
||||
row: 10
|
||||
column: 16
|
||||
end_location:
|
||||
row: 10
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: b
|
||||
location:
|
||||
row: 10
|
||||
column: 19
|
||||
end_location:
|
||||
row: 10
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: a
|
||||
location:
|
||||
row: 13
|
||||
column: 16
|
||||
end_location:
|
||||
row: 13
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: b
|
||||
location:
|
||||
row: 13
|
||||
column: 19
|
||||
end_location:
|
||||
row: 13
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: args
|
||||
location:
|
||||
row: 13
|
||||
column: 23
|
||||
end_location:
|
||||
row: 13
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: kwargs
|
||||
location:
|
||||
row: 13
|
||||
column: 31
|
||||
end_location:
|
||||
row: 13
|
||||
column: 37
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedFunctionArgument: a
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: b
|
||||
location:
|
||||
row: 1
|
||||
column: 9
|
||||
end_location:
|
||||
row: 1
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: a
|
||||
location:
|
||||
row: 5
|
||||
column: 6
|
||||
end_location:
|
||||
row: 5
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: b
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: a
|
||||
location:
|
||||
row: 10
|
||||
column: 16
|
||||
end_location:
|
||||
row: 10
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: b
|
||||
location:
|
||||
row: 10
|
||||
column: 19
|
||||
end_location:
|
||||
row: 10
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: a
|
||||
location:
|
||||
row: 13
|
||||
column: 16
|
||||
end_location:
|
||||
row: 13
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: b
|
||||
location:
|
||||
row: 13
|
||||
column: 19
|
||||
end_location:
|
||||
row: 13
|
||||
column: 20
|
||||
fix: ~
|
||||
|
||||
@@ -590,7 +590,6 @@ mod tests {
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -612,7 +611,6 @@ mod tests {
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -635,7 +633,6 @@ mod tests {
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use rustpython_ast::{Location, Stmt};
|
||||
use textwrap::{dedent, indent};
|
||||
|
||||
use crate::ast::helpers::{count_trailing_lines, match_leading_content, match_trailing_content};
|
||||
use crate::ast::helpers::{
|
||||
count_trailing_lines, followed_by_multi_statement_line, preceded_by_multi_statement_line,
|
||||
};
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::whitespace::leading_space;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::CheckKind;
|
||||
use crate::isort::track::Block;
|
||||
use crate::isort::{comments, format_imports};
|
||||
use crate::settings::flags;
|
||||
use crate::{Check, Settings, SourceCodeLocator};
|
||||
|
||||
fn extract_range(body: &[&Stmt]) -> Range {
|
||||
@@ -32,13 +35,21 @@ pub fn check_imports(
|
||||
block: &Block,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Check> {
|
||||
let indentation = locator.slice_source_code_range(&extract_indentation_range(&block.imports));
|
||||
let indentation = leading_space(&indentation);
|
||||
|
||||
let range = extract_range(&block.imports);
|
||||
|
||||
// Special-cases: there's leading or trailing content in the import block. These
|
||||
// are too hard to get right, and relatively rare, so flag but don't fix.
|
||||
if preceded_by_multi_statement_line(block.imports.first().unwrap(), locator)
|
||||
|| followed_by_multi_statement_line(block.imports.last().unwrap(), locator)
|
||||
{
|
||||
return Some(Check::new(CheckKind::UnsortedImports, range));
|
||||
}
|
||||
|
||||
// Extract comments. Take care to grab any inline comments from the last line.
|
||||
let comments = comments::collect_comments(
|
||||
&Range {
|
||||
@@ -48,9 +59,6 @@ pub fn check_imports(
|
||||
locator,
|
||||
);
|
||||
|
||||
// Special-cases: there's leading or trailing content in the import block.
|
||||
let has_leading_content = match_leading_content(block.imports.first().unwrap(), locator);
|
||||
let has_trailing_content = match_trailing_content(block.imports.last().unwrap(), locator);
|
||||
let num_trailing_lines = if block.trailer.is_none() {
|
||||
0
|
||||
} else {
|
||||
@@ -70,46 +78,25 @@ pub fn check_imports(
|
||||
settings.isort.force_wrap_aliases,
|
||||
);
|
||||
|
||||
if has_leading_content || has_trailing_content {
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
let range = Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
|
||||
};
|
||||
let actual = dedent(&locator.slice_source_code_range(&range));
|
||||
if actual == expected {
|
||||
None
|
||||
} else {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
let mut content = String::new();
|
||||
if has_leading_content {
|
||||
content.push('\n');
|
||||
}
|
||||
content.push_str(&indent(&expected, indentation));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
{
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
// Preserve leading prefix (but put the imports on a new line).
|
||||
if has_leading_content {
|
||||
range.location
|
||||
} else {
|
||||
Location::new(range.location.row(), 0)
|
||||
},
|
||||
// TODO(charlie): Preserve trailing suffixes. Right now, we strip them.
|
||||
Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
|
||||
indent(&expected, indentation),
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Some(check)
|
||||
} else {
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
let range = Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
|
||||
};
|
||||
let actual = dedent(&locator.slice_source_code_range(&range));
|
||||
if actual == expected {
|
||||
None
|
||||
} else {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::replacement(
|
||||
indent(&expected, indentation),
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix:
|
||||
content: "\nimport os\nimport sys\n\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 7
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
@@ -24,12 +17,21 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix:
|
||||
content: "\n import os\n import sys\n"
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 10
|
||||
column: 8
|
||||
end_location:
|
||||
row: 10
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -9,14 +9,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix:
|
||||
content: "import os\nimport sys\n\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
@@ -24,12 +17,5 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix:
|
||||
content: " import os\n import sys\n"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::checks::Check;
|
||||
use crate::linter::check_path;
|
||||
use crate::resolver::Relativity;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
mod ast;
|
||||
@@ -50,6 +51,7 @@ pub mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
mod flake8_debugger;
|
||||
pub mod flake8_errmsg;
|
||||
mod flake8_import_conventions;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
@@ -66,6 +68,7 @@ pub mod logging;
|
||||
pub mod mccabe;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
mod pandas_vet;
|
||||
pub mod pep8_naming;
|
||||
pub mod printer;
|
||||
mod pycodestyle;
|
||||
@@ -87,7 +90,7 @@ pub mod visibility;
|
||||
|
||||
/// Load the relevant `Settings` for a given `Path`.
|
||||
fn resolve(path: &Path) -> Result<Settings> {
|
||||
if let Some(pyproject) = pyproject::find_pyproject_toml(path) {
|
||||
if let Some(pyproject) = pyproject::find_pyproject_toml(path)? {
|
||||
// First priority: `pyproject.toml` in the current `Path`.
|
||||
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
|
||||
} else if let Some(pyproject) = pyproject::find_user_pyproject_toml() {
|
||||
@@ -125,8 +128,8 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
&locator,
|
||||
&directives,
|
||||
&settings,
|
||||
autofix,
|
||||
false,
|
||||
autofix.into(),
|
||||
flags::Noqa::Enabled,
|
||||
)?;
|
||||
|
||||
Ok(checks)
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::code_gen::SourceGenerator;
|
||||
use crate::directives::Directives;
|
||||
use crate::message::{Message, Source};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{cache, directives, fs, rustpython_helpers};
|
||||
|
||||
@@ -55,8 +55,8 @@ pub(crate) fn check_path(
|
||||
locator: &SourceCodeLocator,
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
ignore_noqa: bool,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
) -> Result<Vec<Check>> {
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
@@ -83,7 +83,15 @@ pub(crate) fn check_path(
|
||||
match rustpython_helpers::parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
if use_ast {
|
||||
checks.extend(check_ast(&python_ast, locator, settings, autofix, path));
|
||||
checks.extend(check_ast(
|
||||
&python_ast,
|
||||
locator,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
noqa,
|
||||
path,
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
checks.extend(check_imports(
|
||||
@@ -117,7 +125,7 @@ pub(crate) fn check_path(
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
ignore_noqa,
|
||||
noqa,
|
||||
);
|
||||
|
||||
// Create path ignores.
|
||||
@@ -197,8 +205,8 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
isort: directives.isort,
|
||||
},
|
||||
settings,
|
||||
false,
|
||||
true,
|
||||
flags::Autofix::Disabled,
|
||||
flags::Noqa::Disabled,
|
||||
)?;
|
||||
|
||||
add_noqa(
|
||||
@@ -285,7 +293,7 @@ fn lint(
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
false,
|
||||
flags::Noqa::Enabled,
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
@@ -324,7 +332,7 @@ fn lint(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<Check>> {
|
||||
pub fn test_path(path: &Path, settings: &Settings) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
@@ -340,7 +348,7 @@ pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
false,
|
||||
flags::Autofix::Enabled,
|
||||
flags::Noqa::Enabled,
|
||||
)
|
||||
}
|
||||
|
||||
62
src/main.rs
62
src/main.rs
@@ -20,7 +20,7 @@ use ::ruff::autofix::fixer;
|
||||
use ::ruff::cli::{extract_log_level, Cli};
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::printer::Printer;
|
||||
use ::ruff::resolver::Strategy;
|
||||
use ::ruff::resolver::PyprojectDiscovery;
|
||||
use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::SerializationFormat;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
@@ -33,38 +33,41 @@ use colored::Colorize;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use path_absolutize::path_dedot;
|
||||
use ruff::cli::Overrides;
|
||||
use ruff::resolver::{resolve_settings, Relativity};
|
||||
use ruff::resolver::{resolve_settings, FileDiscovery, Relativity};
|
||||
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
fn resolve(config: Option<PathBuf>, overrides: &Overrides) -> Result<Strategy> {
|
||||
fn resolve(config: Option<PathBuf>, overrides: &Overrides) -> Result<PyprojectDiscovery> {
|
||||
if let Some(pyproject) = config {
|
||||
// First priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
Ok(Strategy::Fixed(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_pyproject_toml(path_dedot::CWD.as_path()) {
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_pyproject_toml(path_dedot::CWD.as_path())? {
|
||||
// Second priority: find a `pyproject.toml` file in the current working path,
|
||||
// and resolve all paths relative to that directory. (With
|
||||
// `Strategy::Hierarchical`, we'll end up finding the "closest" `pyproject.toml`
|
||||
// file for every Python file later on, so these act as the "default" settings.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
Ok(Strategy::Hierarchical(settings))
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_user_pyproject_toml() {
|
||||
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
||||
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
||||
// these act as the "default" settings.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
Ok(Strategy::Hierarchical(settings))
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else {
|
||||
// Fallback: load Ruff's default settings, and resolve all paths relative to the
|
||||
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
let settings = Settings::from_configuration(Configuration::default(), &path_dedot::CWD)?;
|
||||
Ok(Strategy::Hierarchical(settings))
|
||||
let mut config = Configuration::default();
|
||||
// Apply command-line options that override defaults.
|
||||
config.apply(overrides.clone());
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +87,19 @@ fn inner_main() -> Result<ExitCode> {
|
||||
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let strategy = resolve(cli.config, &overrides)?;
|
||||
let pyproject_strategy = resolve(cli.config, &overrides)?;
|
||||
|
||||
// Extract options that are included in `Settings`, but only apply at the top
|
||||
// level.
|
||||
let (fix, format) = match &strategy {
|
||||
Strategy::Fixed(settings) => (settings.fix, settings.format),
|
||||
Strategy::Hierarchical(settings) => (settings.fix, settings.format),
|
||||
let file_strategy = FileDiscovery {
|
||||
respect_gitignore: match &pyproject_strategy {
|
||||
PyprojectDiscovery::Fixed(settings) => settings.respect_gitignore,
|
||||
PyprojectDiscovery::Hierarchical(settings) => settings.respect_gitignore,
|
||||
},
|
||||
};
|
||||
let (fix, format) = match &pyproject_strategy {
|
||||
PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.format),
|
||||
PyprojectDiscovery::Hierarchical(settings) => (settings.fix, settings.format),
|
||||
};
|
||||
let autofix = if fix {
|
||||
fixer::Mode::Apply
|
||||
@@ -105,11 +114,11 @@ fn inner_main() -> Result<ExitCode> {
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if cli.show_settings {
|
||||
commands::show_settings(&cli.files, &strategy, &overrides)?;
|
||||
commands::show_settings(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if cli.show_files {
|
||||
commands::show_files(&cli.files, &strategy, &overrides)?;
|
||||
commands::show_files(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
@@ -141,7 +150,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||
|
||||
let messages = commands::run(
|
||||
&cli.files,
|
||||
&strategy,
|
||||
&pyproject_strategy,
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
cache_enabled,
|
||||
&fixer::Mode::None,
|
||||
@@ -170,7 +180,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||
|
||||
let messages = commands::run(
|
||||
&cli.files,
|
||||
&strategy,
|
||||
&pyproject_strategy,
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
cache_enabled,
|
||||
&fixer::Mode::None,
|
||||
@@ -182,12 +193,14 @@ fn inner_main() -> Result<ExitCode> {
|
||||
}
|
||||
}
|
||||
} else if cli.add_noqa {
|
||||
let modifications = commands::add_noqa(&cli.files, &strategy, &overrides)?;
|
||||
let modifications =
|
||||
commands::add_noqa(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Added {modifications} noqa directives.");
|
||||
}
|
||||
} else if cli.autoformat {
|
||||
let modifications = commands::autoformat(&cli.files, &strategy, &overrides)?;
|
||||
let modifications =
|
||||
commands::autoformat(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Formatted {modifications} files.");
|
||||
}
|
||||
@@ -198,9 +211,16 @@ fn inner_main() -> Result<ExitCode> {
|
||||
let diagnostics = if is_stdin {
|
||||
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
||||
let path = Path::new(&filename);
|
||||
commands::run_stdin(&strategy, path, &autofix)?
|
||||
commands::run_stdin(&pyproject_strategy, path, &autofix)?
|
||||
} else {
|
||||
commands::run(&cli.files, &strategy, &overrides, cache_enabled, &autofix)?
|
||||
commands::run(
|
||||
&cli.files,
|
||||
&pyproject_strategy,
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
cache_enabled,
|
||||
&autofix,
|
||||
)?
|
||||
};
|
||||
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use rustpython_ast::{ExcepthandlerKind, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::helpers::identifier_range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn get_complexity_number(stmts: &[Stmt]) -> usize {
|
||||
let mut complexity = 0;
|
||||
@@ -59,12 +60,13 @@ pub fn function_is_too_complex(
|
||||
name: &str,
|
||||
body: &[Stmt],
|
||||
max_complexity: usize,
|
||||
locator: &SourceCodeLocator,
|
||||
) -> Option<Check> {
|
||||
let complexity = get_complexity_number(body) + 1;
|
||||
if complexity > max_complexity {
|
||||
Some(Check::new(
|
||||
CheckKind::FunctionIsTooComplex(name.to_string(), complexity),
|
||||
Range::from_located(stmt),
|
||||
identifier_range(stmt, locator),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -23,7 +23,6 @@ mod tests {
|
||||
mccabe: mccabe::settings::Settings { max_complexity },
|
||||
..Settings::for_rules(vec![CheckCode::C901])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user