Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9161b866b5 | ||
|
|
38141a6f14 | ||
|
|
aa5402fc0e | ||
|
|
99f077aa4e | ||
|
|
7f25d1ec70 | ||
|
|
360b033e04 | ||
|
|
247dcc9f9c | ||
|
|
c86e52193c | ||
|
|
efdc4e801d | ||
|
|
f8f2eeed35 | ||
|
|
8fa414b67e | ||
|
|
484d7a30bd | ||
|
|
fb681c614a | ||
|
|
06ed125771 | ||
|
|
74668915b0 | ||
|
|
6da3de25ba |
9
.github/workflows/ci.yaml
vendored
9
.github/workflows/ci.yaml
vendored
@@ -20,6 +20,9 @@ jobs:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
@@ -33,6 +36,12 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo build --all --release
|
||||
- run: ./target/release/ruff_dev generate-rules-table
|
||||
- run: ./target/release/ruff_dev generate-options
|
||||
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. You may have to rerun 'cargo dev generate-options' and/or 'cargo dev generate-rules-table'."
|
||||
- run: ./target/release/ruff_dev generate-check-code-prefix && cargo fmt -- src/checks_gen.rs
|
||||
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. You may have to rerun 'cargo dev generate-check-code-prefix'."
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs
|
||||
|
||||
cargo_fmt:
|
||||
name: "cargo fmt"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.174
|
||||
rev: v0.0.176
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.174-dev.0"
|
||||
version = "0.0.176-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1821,7 +1821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1874,7 +1874,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1892,7 +1892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1935,7 +1935,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -1945,7 +1945,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -1968,7 +1968,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -1985,7 +1985,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -41,11 +41,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.174", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.176", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
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" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -438,6 +438,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-simplify, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Martin Thoma
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- isort, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
22
README.md
22
README.md
@@ -41,6 +41,7 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Jupyter Server](https://github.com/jupyter-server/jupyter_server)
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -89,6 +90,7 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
1. [flake8-print (T20)](#flake8-print-t20)
|
||||
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
||||
1. [flake8-return (RET)](#flake8-return-ret)
|
||||
1. [flake8-simplify (SIM)](#flake8-simplify-sim)
|
||||
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
|
||||
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
|
||||
1. [eradicate (ERA)](#eradicate-era)
|
||||
@@ -153,7 +155,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.174
|
||||
rev: v0.0.176
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -368,7 +370,7 @@ For targeted exclusions across entire files (e.g., "Ignore all F841 violations i
|
||||
### "Action Comments"
|
||||
|
||||
Ruff respects `isort`'s ["Action Comments"](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
|
||||
(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `isort: split`), which
|
||||
(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which
|
||||
enable selectively enabling and disabling import sorting for blocks of code and other inline
|
||||
configuration.
|
||||
|
||||
@@ -418,14 +420,14 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F501 | PercentFormatInvalidFormat | '...' % ... has invalid format string: ... | |
|
||||
| F502 | PercentFormatExpectedMapping | '...' % ... expected mapping but got sequence | |
|
||||
| F503 | PercentFormatExpectedSequence | '...' % ... expected sequence but got mapping | |
|
||||
| F504 | PercentFormatExtraNamedArguments | '...' % ... has unused named argument(s): ... | |
|
||||
| F504 | PercentFormatExtraNamedArguments | '...' % ... has unused named argument(s): ... | 🛠 |
|
||||
| F505 | PercentFormatMissingArgument | '...' % ... is missing argument(s) for placeholder(s): ... | |
|
||||
| F506 | PercentFormatMixedPositionalAndNamed | '...' % ... has mixed positional and named placeholders | |
|
||||
| F507 | PercentFormatPositionalCountMismatch | '...' % ... has 4 placeholder(s) but 2 substitution(s) | |
|
||||
| F508 | PercentFormatStarRequiresSequence | '...' % ... `*` specifier requires sequence | |
|
||||
| F509 | PercentFormatUnsupportedFormatCharacter | '...' % ... has unsupported format character 'c' | |
|
||||
| F521 | StringDotFormatInvalidFormat | '...'.format(...) has invalid format string: ... | |
|
||||
| F522 | StringDotFormatExtraNamedArguments | '...'.format(...) has unused named argument(s): ... | |
|
||||
| F522 | StringDotFormatExtraNamedArguments | '...'.format(...) has unused named argument(s): ... | 🛠 |
|
||||
| F523 | StringDotFormatExtraPositionalArguments | '...'.format(...) has unused arguments at position(s): ... | |
|
||||
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
|
||||
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
|
||||
@@ -715,7 +717,7 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
|
||||
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
|
||||
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
|
||||
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
|
||||
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
|
||||
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | 🛠 |
|
||||
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
|
||||
@@ -770,6 +772,14 @@ For more, see [flake8-return](https://pypi.org/project/flake8-return/1.2.0/) on
|
||||
| RET507 | SuperfluousElseContinue | Unnecessary `else` after `continue` statement | |
|
||||
| RET508 | SuperfluousElseBreak | Unnecessary `else` after `break` statement | |
|
||||
|
||||
### flake8-simplify (SIM)
|
||||
|
||||
For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| SIM118 | KeyInDict | Use 'key in dict' instead of 'key in dict.keys() | 🛠 |
|
||||
|
||||
### flake8-tidy-imports (TID)
|
||||
|
||||
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
|
||||
@@ -815,6 +825,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
|
||||
| PLC0414 | UselessImportAlias | Import alias does not rename original package | 🛠 |
|
||||
| PLC2201 | MisplacedComparisonConstant | Comparison should be ... | 🛠 |
|
||||
| PLC3002 | UnnecessaryDirectLambdaCall | Lambda expression called directly. Execute the expression inline instead. | |
|
||||
| PLE0117 | NonlocalWithoutBinding | Nonlocal name `...` found without binding | |
|
||||
| PLE0118 | UsedPriorGlobalDeclaration | Name `...` is used prior to global declaration on line 1 | |
|
||||
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |
|
||||
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
|
||||
@@ -822,6 +833,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
|
||||
| PLR1701 | ConsiderMergingIsinstance | Merge these isinstance calls: `isinstance(..., (...))` | |
|
||||
| PLR1722 | UseSysExit | Use `sys.exit()` instead of `exit` | 🛠 |
|
||||
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
|
||||
| PLW0602 | GlobalVariableNotAssigned | Using global for `...` but no assignment is done | |
|
||||
|
||||
### Ruff-specific rules (RUF)
|
||||
|
||||
|
||||
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.174"
|
||||
version = "0.0.176"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
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=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
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=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
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=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
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=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=2edd0d264c50c7807bcff03a52a6509e8b7f187f#2edd0d264c50c7807bcff03a52a6509e8b7f187f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.174-dev.0"
|
||||
version = "0.0.176-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -18,6 +18,7 @@ pub enum Plugin {
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Return,
|
||||
Flake8Simplify,
|
||||
Flake8TidyImports,
|
||||
McCabe,
|
||||
PEP8Naming,
|
||||
@@ -41,6 +42,7 @@ impl FromStr for Plugin {
|
||||
"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),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
@@ -65,6 +67,7 @@ impl Plugin {
|
||||
Plugin::Flake8Print => CheckCodePrefix::T2,
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Return => CheckCodePrefix::RET,
|
||||
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::McCabe => CheckCodePrefix::C9,
|
||||
Plugin::PEP8Naming => CheckCodePrefix::N,
|
||||
@@ -101,6 +104,7 @@ impl Plugin {
|
||||
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::McCabe => vec![CheckCodePrefix::C9],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
@@ -397,6 +401,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8Print,
|
||||
Plugin::Flake8Quotes,
|
||||
Plugin::Flake8Return,
|
||||
Plugin::Flake8Simplify,
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::PEP8Naming,
|
||||
Plugin::Pyupgrade,
|
||||
|
||||
12
resources/test/fixtures/flake8_simplify/SIM118.py
vendored
Normal file
12
resources/test/fixtures/flake8_simplify/SIM118.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
key in dict.keys() # SIM118
|
||||
|
||||
foo["bar"] in dict.keys() # SIM118
|
||||
|
||||
foo() in dict.keys() # SIM118
|
||||
|
||||
for key in dict.keys(): # SIM118
|
||||
pass
|
||||
|
||||
for key in list(dict.keys()):
|
||||
if some_property(key):
|
||||
del dict[key]
|
||||
3
resources/test/fixtures/pyflakes/F504.py
vendored
3
resources/test/fixtures/pyflakes/F504.py
vendored
@@ -4,3 +4,6 @@ a = "wrong"
|
||||
|
||||
hidden = {"a": "!"}
|
||||
"%(a)s %(c)s" % {"x": 1, **hidden} # Ok (cannot see through splat)
|
||||
|
||||
"%(a)s" % {"a": 1, r"b": "!"} # F504 ("b" not used)
|
||||
"%(a)s" % {'a': 1, u"b": "!"} # F504 ("b" not used)
|
||||
|
||||
1
resources/test/fixtures/pyflakes/F541.py
vendored
1
resources/test/fixtures/pyflakes/F541.py
vendored
@@ -9,3 +9,4 @@ e = (
|
||||
)
|
||||
|
||||
g = f"ghi{123:{45}}"
|
||||
h = "x" "y" f"z"
|
||||
|
||||
17
resources/test/fixtures/pyflakes/F821_6.py
vendored
Normal file
17
resources/test/fixtures/pyflakes/F821_6.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Test: annotated global."""
|
||||
|
||||
|
||||
n: int
|
||||
|
||||
|
||||
def f():
|
||||
print(n)
|
||||
|
||||
|
||||
def g():
|
||||
global n
|
||||
n = 1
|
||||
|
||||
|
||||
g()
|
||||
f()
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.174"
|
||||
version = "0.0.176"
|
||||
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 = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
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" }
|
||||
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.174"
|
||||
version = "0.0.176"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
137
src/check_ast.rs
137
src/check_ast.rs
@@ -37,7 +37,7 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
|
||||
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_tidy_imports,
|
||||
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,
|
||||
};
|
||||
@@ -1059,6 +1059,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PLW0120) {
|
||||
pylint::plugins::useless_else_on_loop(self, stmt, body, orelse);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::SIM118) {
|
||||
flake8_simplify::plugins::key_in_dict_for(self, target, iter);
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
@@ -1155,7 +1158,13 @@ where
|
||||
// If any global bindings don't already exist in the global scope, add it.
|
||||
let globals = operations::extract_globals(body);
|
||||
for (name, stmt) in operations::extract_globals(body) {
|
||||
if !self.scopes[GLOBAL_SCOPE_INDEX].values.contains_key(name) {
|
||||
if self.scopes[GLOBAL_SCOPE_INDEX]
|
||||
.values
|
||||
.get(name)
|
||||
.map_or(true, |index| {
|
||||
matches!(self.bindings[*index].kind, BindingKind::Annotation)
|
||||
})
|
||||
{
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
@@ -1207,7 +1216,13 @@ where
|
||||
// If any global bindings don't already exist in the global scope, add it.
|
||||
let globals = operations::extract_globals(body);
|
||||
for (name, stmt) in &globals {
|
||||
if !self.scopes[GLOBAL_SCOPE_INDEX].values.contains_key(name) {
|
||||
if self.scopes[GLOBAL_SCOPE_INDEX]
|
||||
.values
|
||||
.get(name)
|
||||
.map_or(true, |index| {
|
||||
matches!(self.bindings[*index].kind, BindingKind::Annotation)
|
||||
})
|
||||
{
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
@@ -1481,41 +1496,28 @@ where
|
||||
}
|
||||
Ok(summary) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F522) {
|
||||
if let Some(check) = pyflakes::checks::string_dot_format_extra_named_arguments(
|
||||
pyflakes::plugins::string_dot_format_extra_named_arguments(self,
|
||||
&summary, keywords, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F523) {
|
||||
if let Some(check) = pyflakes::checks::string_dot_format_extra_positional_arguments(
|
||||
pyflakes::plugins::string_dot_format_extra_positional_arguments(
|
||||
self,
|
||||
&summary, args, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F524) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_missing_argument(
|
||||
&summary, args, keywords, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::string_dot_format_missing_argument(
|
||||
self, &summary, args, keywords, location,
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F525) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_mixing_automatic(
|
||||
&summary, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::string_dot_format_mixing_automatic(
|
||||
self, &summary, location,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1966,67 +1968,39 @@ where
|
||||
}
|
||||
Ok(summary) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F502) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_expected_mapping(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_expected_mapping(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F503) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_expected_sequence(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_expected_sequence(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F504) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_extra_named_arguments(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_extra_named_arguments(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F505) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_missing_arguments(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_missing_arguments(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F506) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_mixed_positional_and_named(
|
||||
&summary, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_mixed_positional_and_named(
|
||||
self, &summary, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F507) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_positional_count_mismatch(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_positional_count_mismatch(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F508) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_star_requires_sequence(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
pyflakes::plugins::percent_format_star_requires_sequence(
|
||||
self, &summary, right, location,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2119,6 +2093,16 @@ where
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::SIM118) {
|
||||
flake8_simplify::plugins::key_in_dict_compare(
|
||||
self,
|
||||
expr,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
@@ -2758,6 +2742,7 @@ impl<'a> Checker<'a> {
|
||||
let mut first_iter = true;
|
||||
let mut in_generator = false;
|
||||
let mut import_starred = false;
|
||||
|
||||
for scope_index in self.scope_stack.iter().rev() {
|
||||
let scope = &self.scopes[*scope_index];
|
||||
|
||||
|
||||
@@ -206,6 +206,8 @@ pub enum CheckCode {
|
||||
YTT301,
|
||||
YTT302,
|
||||
YTT303,
|
||||
// flake8-simplify
|
||||
SIM118,
|
||||
// pyupgrade
|
||||
UP001,
|
||||
UP003,
|
||||
@@ -337,6 +339,7 @@ pub enum CheckCategory {
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Return,
|
||||
Flake8Simplify,
|
||||
Flake8TidyImports,
|
||||
Flake8UnusedArguments,
|
||||
Eradicate,
|
||||
@@ -377,6 +380,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
CheckCategory::Flake8Return => "flake8-return",
|
||||
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
|
||||
CheckCategory::Flake8Simplify => "flake8-simplify",
|
||||
CheckCategory::Flake8UnusedArguments => "flake8-unused-arguments",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::McCabe => "mccabe",
|
||||
@@ -406,6 +410,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
|
||||
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
CheckCategory::Flake8Simplify => vec![CheckCodePrefix::SIM],
|
||||
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
|
||||
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
|
||||
CheckCategory::Isort => vec![CheckCodePrefix::I],
|
||||
@@ -481,6 +486,10 @@ impl CheckCategory {
|
||||
"https://pypi.org/project/flake8-return/1.2.0/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8Simplify => Some((
|
||||
"https://pypi.org/project/flake8-simplify/0.19.3/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8TidyImports => Some((
|
||||
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
|
||||
&Platform::PyPI,
|
||||
@@ -746,6 +755,8 @@ pub enum CheckKind {
|
||||
SysVersion0Referenced,
|
||||
SysVersionCmpStr10,
|
||||
SysVersionSlice1Referenced,
|
||||
// flake8-simplify
|
||||
KeyInDict(String, String),
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UselessMetaclassType,
|
||||
@@ -1082,6 +1093,8 @@ impl CheckCode {
|
||||
CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced,
|
||||
// flake8-blind-except
|
||||
CheckCode::BLE001 => CheckKind::BlindExcept("Exception".to_string()),
|
||||
// flake8-simplify
|
||||
CheckCode::SIM118 => CheckKind::KeyInDict("key".to_string(), "dict".to_string()),
|
||||
// pyupgrade
|
||||
CheckCode::UP001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::UP003 => CheckKind::TypeOfPrimitive(Primitive::Str),
|
||||
@@ -1442,6 +1455,7 @@ impl CheckCode {
|
||||
CheckCode::S105 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S106 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::SIM118 => CheckCategory::Flake8Simplify,
|
||||
CheckCode::T100 => CheckCategory::Flake8Debugger,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
@@ -1650,6 +1664,8 @@ impl CheckKind {
|
||||
CheckKind::SysVersion0Referenced => &CheckCode::YTT301,
|
||||
CheckKind::SysVersionCmpStr10 => &CheckCode::YTT302,
|
||||
CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303,
|
||||
// flake8-simplify
|
||||
CheckKind::KeyInDict(..) => &CheckCode::SIM118,
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(_) => &CheckCode::UP003,
|
||||
CheckKind::UselessMetaclassType => &CheckCode::UP001,
|
||||
@@ -2316,6 +2332,10 @@ impl CheckKind {
|
||||
CheckKind::SysVersionSlice1Referenced => {
|
||||
"`sys.version[:1]` referenced (python10), use `sys.version_info`".to_string()
|
||||
}
|
||||
// flake8-simplify
|
||||
CheckKind::KeyInDict(key, dict) => {
|
||||
format!("Use '{key} in {dict}' instead of '{key} in {dict}.keys()")
|
||||
}
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -2660,6 +2680,7 @@ impl CheckKind {
|
||||
| CheckKind::ImplicitReturn
|
||||
| CheckKind::ImplicitReturnValue
|
||||
| CheckKind::IsLiteral
|
||||
| CheckKind::KeyInDict(..)
|
||||
| CheckKind::MisplacedComparisonConstant(..)
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::NewLineAfterSectionName(..)
|
||||
@@ -2675,6 +2696,7 @@ impl CheckKind {
|
||||
| CheckKind::NotIsTest
|
||||
| CheckKind::OneBlankLineAfterClass(..)
|
||||
| CheckKind::OneBlankLineBeforeClass(..)
|
||||
| CheckKind::PercentFormatExtraNamedArguments(..)
|
||||
| CheckKind::PEP3120UnnecessaryCodingComment
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
@@ -2687,9 +2709,11 @@ impl CheckKind {
|
||||
| CheckKind::SectionUnderlineMatchesSectionLength(..)
|
||||
| CheckKind::SectionUnderlineNotOverIndented(..)
|
||||
| CheckKind::SetAttrWithConstant
|
||||
| CheckKind::StringDotFormatExtraNamedArguments(..)
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TrueFalseComparison(..)
|
||||
| CheckKind::TypeOfPrimitive(..)
|
||||
| CheckKind::UnnecessaryCallAroundSorted(..)
|
||||
| CheckKind::UnnecessaryCollectionCall(..)
|
||||
| CheckKind::UnnecessaryComprehension(..)
|
||||
| CheckKind::UnnecessaryEncodeUTF8
|
||||
|
||||
@@ -382,6 +382,10 @@ pub enum CheckCodePrefix {
|
||||
S105,
|
||||
S106,
|
||||
S107,
|
||||
SIM,
|
||||
SIM1,
|
||||
SIM11,
|
||||
SIM118,
|
||||
T,
|
||||
T1,
|
||||
T10,
|
||||
@@ -1494,6 +1498,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::S105 => vec![CheckCode::S105],
|
||||
CheckCodePrefix::S106 => vec![CheckCode::S106],
|
||||
CheckCodePrefix::S107 => vec![CheckCode::S107],
|
||||
CheckCodePrefix::SIM => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM1 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM11 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM118 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T1 => vec![CheckCode::T100],
|
||||
CheckCodePrefix::T10 => vec![CheckCode::T100],
|
||||
@@ -2203,6 +2211,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::S105 => SuffixLength::Three,
|
||||
CheckCodePrefix::S106 => SuffixLength::Three,
|
||||
CheckCodePrefix::S107 => SuffixLength::Three,
|
||||
CheckCodePrefix::SIM => SuffixLength::Zero,
|
||||
CheckCodePrefix::SIM1 => SuffixLength::One,
|
||||
CheckCodePrefix::SIM11 => SuffixLength::Two,
|
||||
CheckCodePrefix::SIM118 => SuffixLength::Three,
|
||||
CheckCodePrefix::T => SuffixLength::Zero,
|
||||
CheckCodePrefix::T1 => SuffixLength::One,
|
||||
CheckCodePrefix::T10 => SuffixLength::Two,
|
||||
@@ -2303,6 +2315,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::RET,
|
||||
CheckCodePrefix::RUF,
|
||||
CheckCodePrefix::S,
|
||||
CheckCodePrefix::SIM,
|
||||
CheckCodePrefix::T,
|
||||
CheckCodePrefix::TID,
|
||||
CheckCodePrefix::UP,
|
||||
|
||||
113
src/cli.rs
113
src/cli.rs
@@ -47,42 +47,44 @@ pub struct Cli {
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --select, but adds additional error codes on top of the selected
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<FilePattern>,
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the
|
||||
/// excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
/// List of error codes to treat as eligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub fixable: Vec<CheckCodePrefix>,
|
||||
pub fixable: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of error codes to treat as ineligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub unfixable: Vec<CheckCodePrefix>,
|
||||
pub unfixable: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<PatternPrefixPair>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum)]
|
||||
pub format: Option<SerializationFormat>,
|
||||
/// Show violations with source code.
|
||||
#[arg(long)]
|
||||
pub show_source: bool,
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
show_source: bool,
|
||||
#[clap(long, overrides_with("show_source"), hide = true)]
|
||||
no_show_source: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
@@ -121,9 +123,47 @@ pub struct Cli {
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
// See: https://github.com/clap-rs/clap/issues/3146
|
||||
pub fn fix(&self) -> Option<bool> {
|
||||
resolve_bool_arg(self.fix, self.no_fix)
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> (Arguments, Overrides) {
|
||||
(
|
||||
Arguments {
|
||||
add_noqa: self.add_noqa,
|
||||
autoformat: self.autoformat,
|
||||
config: self.config,
|
||||
exit_zero: self.exit_zero,
|
||||
explain: self.explain,
|
||||
files: self.files,
|
||||
generate_shell_completion: self.generate_shell_completion,
|
||||
no_cache: self.no_cache,
|
||||
quiet: self.quiet,
|
||||
show_files: self.show_files,
|
||||
show_settings: self.show_settings,
|
||||
silent: self.silent,
|
||||
stdin_filename: self.stdin_filename,
|
||||
verbose: self.verbose,
|
||||
watch: self.watch,
|
||||
},
|
||||
Overrides {
|
||||
dummy_variable_rgx: self.dummy_variable_rgx,
|
||||
exclude: self.exclude,
|
||||
extend_exclude: self.extend_exclude,
|
||||
extend_ignore: self.extend_ignore,
|
||||
extend_select: self.extend_select,
|
||||
fixable: self.fixable,
|
||||
ignore: self.ignore,
|
||||
line_length: self.line_length,
|
||||
max_complexity: self.max_complexity,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
select: self.select,
|
||||
show_source: resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
target_version: self.target_version,
|
||||
unfixable: self.unfixable,
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
format: self.format,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +176,51 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
/// etc.).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Arguments {
|
||||
pub add_noqa: bool,
|
||||
pub autoformat: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub exit_zero: bool,
|
||||
pub explain: Option<CheckCode>,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
pub no_cache: bool,
|
||||
pub quiet: bool,
|
||||
pub show_files: bool,
|
||||
pub show_settings: bool,
|
||||
pub silent: bool,
|
||||
pub stdin_filename: Option<String>,
|
||||
pub verbose: bool,
|
||||
pub watch: bool,
|
||||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Overrides {
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
pub fixable: Option<Vec<CheckCodePrefix>>,
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub line_length: Option<usize>,
|
||||
pub max_complexity: Option<usize>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
pub show_source: Option<bool>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
pub unfixable: Option<Vec<CheckCodePrefix>>,
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub fix: Option<bool>,
|
||||
pub format: Option<SerializationFormat>,
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
pub fn extract_log_level(cli: &Cli) -> LogLevel {
|
||||
pub fn extract_log_level(cli: &Arguments) -> LogLevel {
|
||||
if cli.silent {
|
||||
LogLevel::Silent
|
||||
} else if cli.quiet {
|
||||
|
||||
29
src/flake8_simplify/mod.rs
Normal file
29
src/flake8_simplify/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::SIM118, Path::new("SIM118.py"); "SIM118")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_simplify")
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
74
src/flake8_simplify/plugins/key_in_dict.rs
Normal file
74
src/flake8_simplify/plugins/key_in_dict.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use rustpython_ast::{Cmpop, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
/// SIM118
|
||||
fn key_in_dict(checker: &mut Checker, left: &Expr, right: &Expr, range: Range) {
|
||||
let ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} = &right.node else {
|
||||
return;
|
||||
};
|
||||
if !(args.is_empty() && keywords.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ExprKind::Attribute { attr, value, .. } = &func.node else {
|
||||
return;
|
||||
};
|
||||
if attr != "keys" {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::KeyInDict(left.to_string(), value.to_string()),
|
||||
range,
|
||||
);
|
||||
if checker.patch(&CheckCode::SIM118) {
|
||||
let content = right.to_string().replace(".keys()", "");
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
right.location,
|
||||
right.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
/// SIM118 in a for loop
|
||||
pub fn key_in_dict_for(checker: &mut Checker, target: &Expr, iter: &Expr) {
|
||||
key_in_dict(
|
||||
checker,
|
||||
target,
|
||||
iter,
|
||||
Range {
|
||||
location: target.location,
|
||||
end_location: iter.end_location.unwrap(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// SIM118 in a comparison
|
||||
pub fn key_in_dict_compare(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
) {
|
||||
if !matches!(ops[..], [Cmpop::In]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if comparators.len() != 1 {
|
||||
return;
|
||||
}
|
||||
let right = comparators.first().unwrap();
|
||||
|
||||
key_in_dict(checker, left, right, Range::from_located(expr));
|
||||
}
|
||||
3
src/flake8_simplify/plugins/mod.rs
Normal file
3
src/flake8_simplify/plugins/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub use key_in_dict::{key_in_dict_compare, key_in_dict_for};
|
||||
|
||||
mod key_in_dict;
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
source: src/flake8_simplify/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
KeyInDict:
|
||||
- key
|
||||
- dict
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
fix:
|
||||
content: dict
|
||||
location:
|
||||
row: 1
|
||||
column: 7
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
- kind:
|
||||
KeyInDict:
|
||||
- "foo['bar']"
|
||||
- dict
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 25
|
||||
fix:
|
||||
content: dict
|
||||
location:
|
||||
row: 3
|
||||
column: 14
|
||||
end_location:
|
||||
row: 3
|
||||
column: 25
|
||||
- kind:
|
||||
KeyInDict:
|
||||
- foo()
|
||||
- dict
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 20
|
||||
fix:
|
||||
content: dict
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 20
|
||||
- kind:
|
||||
KeyInDict:
|
||||
- key
|
||||
- dict
|
||||
location:
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 7
|
||||
column: 22
|
||||
fix:
|
||||
content: dict
|
||||
location:
|
||||
row: 7
|
||||
column: 11
|
||||
end_location:
|
||||
row: 7
|
||||
column: 22
|
||||
|
||||
@@ -53,6 +53,7 @@ mod flake8_import_conventions;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
mod flake8_return;
|
||||
mod flake8_simplify;
|
||||
pub mod flake8_tidy_imports;
|
||||
mod flake8_unused_arguments;
|
||||
pub mod fs;
|
||||
|
||||
76
src/main.rs
76
src/main.rs
@@ -19,7 +19,7 @@ use std::time::Instant;
|
||||
|
||||
use ::ruff::autofix::fixer;
|
||||
use ::ruff::checks::{CheckCode, CheckKind};
|
||||
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
|
||||
use ::ruff::cli::{extract_log_level, Cli};
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
@@ -199,8 +199,7 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> usize {
|
||||
|
||||
fn inner_main() -> Result<ExitCode> {
|
||||
// Extract command-line arguments.
|
||||
let cli = Cli::parse();
|
||||
let fix = cli.fix();
|
||||
let (cli, overrides) = Cli::parse().partition();
|
||||
let log_level = extract_log_level(&cli);
|
||||
set_up_logging(&log_level)?;
|
||||
|
||||
@@ -220,54 +219,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
// Reconcile configuration from pyproject.toml and command-line arguments.
|
||||
let mut configuration =
|
||||
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?;
|
||||
if !cli.exclude.is_empty() {
|
||||
configuration.exclude = cli.exclude;
|
||||
}
|
||||
if !cli.extend_exclude.is_empty() {
|
||||
configuration.extend_exclude = cli.extend_exclude;
|
||||
}
|
||||
if !cli.per_file_ignores.is_empty() {
|
||||
configuration.per_file_ignores = collect_per_file_ignores(cli.per_file_ignores);
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
configuration.select = cli.select;
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
configuration.extend_select = cli.extend_select;
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
configuration.ignore = cli.ignore;
|
||||
}
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
configuration.extend_ignore = cli.extend_ignore;
|
||||
}
|
||||
if !cli.fixable.is_empty() {
|
||||
configuration.fixable = cli.fixable;
|
||||
}
|
||||
if !cli.unfixable.is_empty() {
|
||||
configuration.unfixable = cli.unfixable;
|
||||
}
|
||||
if let Some(format) = cli.format {
|
||||
configuration.format = format;
|
||||
}
|
||||
if let Some(line_length) = cli.line_length {
|
||||
configuration.line_length = line_length;
|
||||
}
|
||||
if let Some(max_complexity) = cli.max_complexity {
|
||||
configuration.mccabe.max_complexity = max_complexity;
|
||||
}
|
||||
if let Some(target_version) = cli.target_version {
|
||||
configuration.target_version = target_version;
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
|
||||
configuration.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
if let Some(fix) = fix {
|
||||
configuration.fix = fix;
|
||||
}
|
||||
if cli.show_source {
|
||||
configuration.show_source = true;
|
||||
}
|
||||
configuration.merge(overrides);
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
eprintln!("Error: specify --show-settings or show-files (not both).");
|
||||
@@ -278,14 +230,16 @@ fn inner_main() -> Result<ExitCode> {
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Extract settings for internal use.
|
||||
let autofix = if configuration.fix {
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
let fix = if configuration.fix {
|
||||
fixer::Mode::Apply
|
||||
} else if matches!(configuration.format, SerializationFormat::Json) {
|
||||
fixer::Mode::Generate
|
||||
} else {
|
||||
fixer::Mode::None
|
||||
};
|
||||
let format = configuration.format;
|
||||
|
||||
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
|
||||
|
||||
// Now that we've inferred the appropriate log level, add some debug
|
||||
@@ -300,7 +254,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, settings.format)?;
|
||||
commands::explain(&code, format)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
@@ -316,9 +270,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
cache_enabled = false;
|
||||
}
|
||||
|
||||
let printer = Printer::new(&settings.format, &log_level);
|
||||
let printer = Printer::new(&format, &log_level);
|
||||
if cli.watch {
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if matches!(fix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
eprintln!("Warning: --fix is not enabled in watch mode.");
|
||||
}
|
||||
if cli.add_noqa {
|
||||
@@ -327,7 +281,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if cli.autoformat {
|
||||
eprintln!("Warning: --autoformat is not enabled in watch mode.");
|
||||
}
|
||||
if settings.format != SerializationFormat::Text {
|
||||
if format != SerializationFormat::Text {
|
||||
eprintln!("Warning: --format 'text' is used in watch mode.");
|
||||
}
|
||||
|
||||
@@ -383,16 +337,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);
|
||||
run_once_stdin(&settings, path, &autofix)?
|
||||
run_once_stdin(&settings, path, &fix)?
|
||||
} else {
|
||||
run_once(&cli.files, &settings, cache_enabled, &autofix)
|
||||
run_once(&cli.files, &settings, cache_enabled, &fix)
|
||||
};
|
||||
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
// unless we're writing fixes via stdin (in which case, the transformed
|
||||
// source code goes to stdout).
|
||||
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
|
||||
printer.write_once(&diagnostics, &autofix)?;
|
||||
if !(is_stdin && matches!(fix, fixer::Mode::Apply)) {
|
||||
printer.write_once(&diagnostics, &fix)?;
|
||||
}
|
||||
|
||||
// Check for updates if we're in a non-silent log level.
|
||||
|
||||
@@ -2,348 +2,12 @@ use std::string::ToString;
|
||||
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Keyword, KeywordData};
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::ast::types::{Binding, BindingKind, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pyflakes::cformat::CFormatSummary;
|
||||
use crate::pyflakes::format::FormatSummary;
|
||||
|
||||
fn has_star_star_kwargs(keywords: &[Keyword]) -> bool {
|
||||
keywords.iter().any(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.is_none()
|
||||
})
|
||||
}
|
||||
|
||||
fn has_star_args(args: &[Expr]) -> bool {
|
||||
args.iter()
|
||||
.any(|a| matches!(&a.node, ExprKind::Starred { .. }))
|
||||
}
|
||||
|
||||
/// F502
|
||||
pub(crate) fn percent_format_expected_mapping(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.keywords.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Tuple, List, Set (+comprehensions)
|
||||
match right.node {
|
||||
ExprKind::List { .. }
|
||||
| ExprKind::Tuple { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::GeneratorExp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatExpectedMapping,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F503
|
||||
pub(crate) fn percent_format_expected_sequence(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional <= 1 {
|
||||
None
|
||||
} else {
|
||||
match right.node {
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatExpectedSequence,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F504
|
||||
pub(crate) fn percent_format_extra_named_arguments(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional > 0 {
|
||||
return None;
|
||||
}
|
||||
let ExprKind::Dict { keys, values } = &right.node else {
|
||||
return None;
|
||||
};
|
||||
if values.len() > keys.len() {
|
||||
return None; // contains **x splat
|
||||
}
|
||||
|
||||
let missing: Vec<&String> = keys
|
||||
.iter()
|
||||
.filter_map(|k| match &k.node {
|
||||
// We can only check that string literals exist
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
if summary.keywords.contains(value) {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatExtraNamedArguments(missing.iter().map(|&s| s.clone()).collect()),
|
||||
location,
|
||||
))
|
||||
}
|
||||
|
||||
/// F505
|
||||
pub(crate) fn percent_format_missing_arguments(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let ExprKind::Dict { keys, values } = &right.node {
|
||||
if values.len() > keys.len() {
|
||||
return None; // contains **x splat
|
||||
}
|
||||
|
||||
let mut keywords = FxHashSet::default();
|
||||
for key in keys {
|
||||
match &key.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
keywords.insert(value);
|
||||
}
|
||||
_ => {
|
||||
return None; // Dynamic keys present
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let missing: Vec<&String> = summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatMissingArgument(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// F506
|
||||
pub(crate) fn percent_format_mixed_positional_and_named(
|
||||
summary: &CFormatSummary,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional == 0 || summary.keywords.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatMixedPositionalAndNamed,
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F507
|
||||
pub(crate) fn percent_format_positional_count_mismatch(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if !summary.keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &right.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } | ExprKind::Set { elts, .. } => {
|
||||
let mut found = 0;
|
||||
for elt in elts {
|
||||
if let ExprKind::Starred { .. } = &elt.node {
|
||||
return None;
|
||||
}
|
||||
found += 1;
|
||||
}
|
||||
|
||||
if found == summary.num_positional {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatPositionalCountMismatch(summary.num_positional, found),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// F508
|
||||
pub(crate) fn percent_format_star_requires_sequence(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.starred {
|
||||
match &right.node {
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatStarRequiresSequence,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// F522
|
||||
pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
summary: &FormatSummary,
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_star_kwargs(keywords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let keywords = keywords.iter().filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
});
|
||||
|
||||
let missing: Vec<&String> = keywords
|
||||
.filter(|&k| !summary.keywords.contains(k))
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatExtraNamedArguments(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F523
|
||||
pub(crate) fn string_dot_format_extra_positional_arguments(
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_args(args) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let missing: Vec<String> = (0..args.len())
|
||||
.filter(|i| !(summary.autos.contains(i) || summary.indexes.contains(i)))
|
||||
.map(|i| i.to_string())
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(missing),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F524
|
||||
pub(crate) fn string_dot_format_missing_argument(
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_args(args) || has_star_star_kwargs(keywords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let keywords: FxHashSet<_> = keywords
|
||||
.iter()
|
||||
.filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let missing: Vec<String> = summary
|
||||
.autos
|
||||
.iter()
|
||||
.chain(summary.indexes.iter())
|
||||
.filter(|&&i| i >= args.len())
|
||||
.map(ToString::to_string)
|
||||
.chain(
|
||||
summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.cloned(),
|
||||
)
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatMissingArguments(missing),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F525
|
||||
pub(crate) fn string_dot_format_mixing_automatic(
|
||||
summary: &FormatSummary,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.autos.is_empty() || summary.indexes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatMixingAutomatic,
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F631
|
||||
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{Codegen, CodegenState, ImportNames, SmallStatement, Statement};
|
||||
use rustpython_ast::Stmt;
|
||||
use libcst_native::{
|
||||
Call, Codegen, CodegenState, Dict, DictElement, Expression, ImportNames, SmallStatement,
|
||||
Statement,
|
||||
};
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{helpers, Fix};
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::cst::matchers::{match_expr, match_module};
|
||||
use crate::python::string::strip_quotes_and_prefixes;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Generate a Fix to remove any unused imports from an `import` statement.
|
||||
/// Generate a `Fix` to remove any unused imports from an `import` statement.
|
||||
pub fn remove_unused_imports(
|
||||
locator: &SourceCodeLocator,
|
||||
unused_imports: &Vec<(&String, &Range)>,
|
||||
@@ -73,3 +77,93 @@ pub fn remove_unused_imports(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove unused keys from format dict.
|
||||
pub fn remove_unused_format_arguments_from_dict(
|
||||
locator: &SourceCodeLocator,
|
||||
unused_arguments: &[&str],
|
||||
stmt: &Expr,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
|
||||
let new_dict = {
|
||||
let Expression::Dict(dict) = &body.value else {
|
||||
bail!("Expected Expression::Dict")
|
||||
};
|
||||
|
||||
Dict {
|
||||
lbrace: dict.lbrace.clone(),
|
||||
lpar: dict.lpar.clone(),
|
||||
rbrace: dict.rbrace.clone(),
|
||||
rpar: dict.rpar.clone(),
|
||||
elements: dict
|
||||
.elements
|
||||
.iter()
|
||||
.filter_map(|e| match e {
|
||||
DictElement::Simple {
|
||||
key: Expression::SimpleString(name),
|
||||
..
|
||||
} if unused_arguments.contains(&strip_quotes_and_prefixes(name.value)) => None,
|
||||
e => Some(e.clone()),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
};
|
||||
|
||||
body.value = Expression::Dict(Box::new(new_dict));
|
||||
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove unused keyword arguments from format call.
|
||||
pub fn remove_unused_keyword_arguments_from_format_call(
|
||||
locator: &SourceCodeLocator,
|
||||
unused_arguments: &[&str],
|
||||
location: Range,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&location);
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
|
||||
let new_call = {
|
||||
let Expression::Call(call) = &body.value else {
|
||||
bail!("Expected Expression::Call")
|
||||
};
|
||||
|
||||
Call {
|
||||
func: call.func.clone(),
|
||||
lpar: call.lpar.clone(),
|
||||
rpar: call.rpar.clone(),
|
||||
whitespace_before_args: call.whitespace_before_args.clone(),
|
||||
whitespace_after_func: call.whitespace_after_func.clone(),
|
||||
args: call
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|e| match &e.keyword {
|
||||
Some(kw) if unused_arguments.contains(&kw.value) => None,
|
||||
_ => Some(e.clone()),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
};
|
||||
|
||||
body.value = Expression::Call(Box::new(new_call));
|
||||
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
location.location,
|
||||
location.end_location,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ mod tests {
|
||||
#[test_case(CheckCode::F821, Path::new("F821_3.py"); "F821_3")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_4.py"); "F821_4")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_5.py"); "F821_5")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_6.py"); "F821_6")]
|
||||
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
|
||||
@@ -411,6 +412,16 @@ mod tests {
|
||||
"#,
|
||||
&[],
|
||||
)?;
|
||||
// TODO(charlie): Extract globals recursively (such that we don't raise F821).
|
||||
flakes(
|
||||
r#"
|
||||
def c(): bar
|
||||
def d():
|
||||
def b():
|
||||
global bar; bar = 1
|
||||
"#,
|
||||
&[CheckCode::F821],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,18 @@ pub use if_tuple::if_tuple;
|
||||
pub use invalid_literal_comparisons::invalid_literal_comparison;
|
||||
pub use invalid_print_syntax::invalid_print_syntax;
|
||||
pub use raise_not_implemented::raise_not_implemented;
|
||||
pub(crate) use strings::{
|
||||
percent_format_expected_mapping, percent_format_expected_sequence,
|
||||
percent_format_extra_named_arguments, percent_format_missing_arguments,
|
||||
percent_format_mixed_positional_and_named, percent_format_positional_count_mismatch,
|
||||
percent_format_star_requires_sequence, string_dot_format_extra_named_arguments,
|
||||
string_dot_format_extra_positional_arguments, string_dot_format_missing_argument,
|
||||
string_dot_format_mixing_automatic,
|
||||
};
|
||||
|
||||
mod assert_tuple;
|
||||
mod if_tuple;
|
||||
mod invalid_literal_comparisons;
|
||||
mod invalid_print_syntax;
|
||||
mod raise_not_implemented;
|
||||
mod strings;
|
||||
|
||||
369
src/pyflakes/plugins/strings.rs
Normal file
369
src/pyflakes/plugins/strings.rs
Normal file
@@ -0,0 +1,369 @@
|
||||
use std::string::ToString;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Keyword, KeywordData};
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pyflakes::cformat::CFormatSummary;
|
||||
use crate::pyflakes::fixes::{
|
||||
remove_unused_format_arguments_from_dict, remove_unused_keyword_arguments_from_format_call,
|
||||
};
|
||||
use crate::pyflakes::format::FormatSummary;
|
||||
|
||||
fn has_star_star_kwargs(keywords: &[Keyword]) -> bool {
|
||||
keywords.iter().any(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.is_none()
|
||||
})
|
||||
}
|
||||
|
||||
fn has_star_args(args: &[Expr]) -> bool {
|
||||
args.iter()
|
||||
.any(|arg| matches!(&arg.node, ExprKind::Starred { .. }))
|
||||
}
|
||||
|
||||
/// F502
|
||||
pub(crate) fn percent_format_expected_mapping(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if !summary.keywords.is_empty() {
|
||||
// Tuple, List, Set (+comprehensions)
|
||||
match right.node {
|
||||
ExprKind::List { .. }
|
||||
| ExprKind::Tuple { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::GeneratorExp { .. } => checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatExpectedMapping,
|
||||
location,
|
||||
)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F503
|
||||
pub(crate) fn percent_format_expected_sequence(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if summary.num_positional > 1
|
||||
&& matches!(
|
||||
right.node,
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. }
|
||||
)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatExpectedSequence,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// F504
|
||||
pub(crate) fn percent_format_extra_named_arguments(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if summary.num_positional > 0 {
|
||||
return;
|
||||
}
|
||||
let ExprKind::Dict { keys, values } = &right.node else {
|
||||
return;
|
||||
};
|
||||
if values.len() > keys.len() {
|
||||
return; // contains **x splat
|
||||
}
|
||||
|
||||
let missing: Vec<&str> = keys
|
||||
.iter()
|
||||
.filter_map(|k| match &k.node {
|
||||
// We can only check that string literals exist
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
if summary.keywords.contains(value) {
|
||||
None
|
||||
} else {
|
||||
Some(value.as_str())
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::PercentFormatExtraNamedArguments(
|
||||
missing.iter().map(|&arg| arg.to_string()).collect(),
|
||||
),
|
||||
location,
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Ok(fix) = remove_unused_format_arguments_from_dict(checker.locator, &missing, right)
|
||||
{
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
/// F505
|
||||
pub(crate) fn percent_format_missing_arguments(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if summary.num_positional > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ExprKind::Dict { keys, values } = &right.node {
|
||||
if values.len() > keys.len() {
|
||||
return; // contains **x splat
|
||||
}
|
||||
|
||||
let mut keywords = FxHashSet::default();
|
||||
for key in keys {
|
||||
match &key.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
keywords.insert(value);
|
||||
}
|
||||
_ => {
|
||||
return; // Dynamic keys present
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let missing: Vec<&String> = summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.collect();
|
||||
|
||||
if !missing.is_empty() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatMissingArgument(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F506
|
||||
pub(crate) fn percent_format_mixed_positional_and_named(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
location: Range,
|
||||
) {
|
||||
if !(summary.num_positional == 0 || summary.keywords.is_empty()) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatMixedPositionalAndNamed,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// F507
|
||||
pub(crate) fn percent_format_positional_count_mismatch(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if !summary.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
match &right.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } | ExprKind::Set { elts, .. } => {
|
||||
let mut found = 0;
|
||||
for elt in elts {
|
||||
if let ExprKind::Starred { .. } = &elt.node {
|
||||
return;
|
||||
}
|
||||
found += 1;
|
||||
}
|
||||
|
||||
if found != summary.num_positional {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatPositionalCountMismatch(summary.num_positional, found),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// F508
|
||||
pub(crate) fn percent_format_star_requires_sequence(
|
||||
checker: &mut Checker,
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) {
|
||||
if summary.starred {
|
||||
match &right.node {
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => checker.add_check(Check::new(
|
||||
CheckKind::PercentFormatStarRequiresSequence,
|
||||
location,
|
||||
)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F522
|
||||
pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
checker: &mut Checker,
|
||||
summary: &FormatSummary,
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) {
|
||||
if has_star_star_kwargs(keywords) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keywords = keywords.iter().filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
});
|
||||
|
||||
let missing: Vec<&str> = keywords
|
||||
.filter_map(|arg| {
|
||||
if summary.keywords.contains(arg) {
|
||||
None
|
||||
} else {
|
||||
Some(arg.as_str())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::StringDotFormatExtraNamedArguments(
|
||||
missing.iter().map(|&arg| arg.to_string()).collect(),
|
||||
),
|
||||
location,
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Ok(fix) =
|
||||
remove_unused_keyword_arguments_from_format_call(checker.locator, &missing, location)
|
||||
{
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
/// F523
|
||||
pub(crate) fn string_dot_format_extra_positional_arguments(
|
||||
checker: &mut Checker,
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) {
|
||||
if has_star_args(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
let missing: Vec<usize> = (0..args.len())
|
||||
.filter(|i| !(summary.autos.contains(i) || summary.indexes.contains(i)))
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(
|
||||
missing
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
location,
|
||||
));
|
||||
}
|
||||
|
||||
/// F524
|
||||
pub(crate) fn string_dot_format_missing_argument(
|
||||
checker: &mut Checker,
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) {
|
||||
if has_star_args(args) || has_star_star_kwargs(keywords) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keywords: FxHashSet<_> = keywords
|
||||
.iter()
|
||||
.filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let missing: Vec<String> = summary
|
||||
.autos
|
||||
.iter()
|
||||
.chain(summary.indexes.iter())
|
||||
.filter(|&&i| i >= args.len())
|
||||
.map(ToString::to_string)
|
||||
.chain(
|
||||
summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.cloned(),
|
||||
)
|
||||
.collect();
|
||||
|
||||
if !missing.is_empty() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::StringDotFormatMissingArguments(missing),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// F525
|
||||
pub(crate) fn string_dot_format_mixing_automatic(
|
||||
checker: &mut Checker,
|
||||
summary: &FormatSummary,
|
||||
location: Range,
|
||||
) {
|
||||
if !(summary.autos.is_empty() || summary.indexes.is_empty()) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::StringDotFormatMixingAutomatic,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,46 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 34
|
||||
fix: ~
|
||||
fix:
|
||||
content: "{a: \"?\", }"
|
||||
location:
|
||||
row: 3
|
||||
column: 16
|
||||
end_location:
|
||||
row: 3
|
||||
column: 34
|
||||
- kind:
|
||||
PercentFormatExtraNamedArguments:
|
||||
- b
|
||||
location:
|
||||
row: 8
|
||||
column: 8
|
||||
end_location:
|
||||
row: 8
|
||||
column: 29
|
||||
fix:
|
||||
content: "{\"a\": 1, }"
|
||||
location:
|
||||
row: 8
|
||||
column: 10
|
||||
end_location:
|
||||
row: 8
|
||||
column: 29
|
||||
- kind:
|
||||
PercentFormatExtraNamedArguments:
|
||||
- b
|
||||
location:
|
||||
row: 9
|
||||
column: 8
|
||||
end_location:
|
||||
row: 9
|
||||
column: 29
|
||||
fix:
|
||||
content: "{'a': 1, }"
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 29
|
||||
|
||||
|
||||
@@ -11,5 +11,12 @@ expression: checks
|
||||
end_location:
|
||||
row: 8
|
||||
column: 32
|
||||
fix: ~
|
||||
fix:
|
||||
content: "{'bar': 1, }"
|
||||
location:
|
||||
row: 8
|
||||
column: 12
|
||||
end_location:
|
||||
row: 8
|
||||
column: 32
|
||||
|
||||
|
||||
@@ -11,7 +11,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 21
|
||||
fix: ~
|
||||
fix:
|
||||
content: "\"{}\".format(1, )"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 21
|
||||
- kind:
|
||||
StringDotFormatExtraNamedArguments:
|
||||
- spam
|
||||
@@ -21,7 +28,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 34
|
||||
fix: ~
|
||||
fix:
|
||||
content: "\"{bar}{}\".format(1, bar=2, )"
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 34
|
||||
- kind:
|
||||
StringDotFormatExtraNamedArguments:
|
||||
- eggs
|
||||
@@ -32,5 +46,12 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 51
|
||||
fix: ~
|
||||
fix:
|
||||
content: "\"{bar:{spam}}\".format(bar=2, spam=3, )"
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 51
|
||||
|
||||
|
||||
@@ -26,4 +26,12 @@ expression: checks
|
||||
row: 7
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: FStringMissingPlaceholders
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 12
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ expression: checks
|
||||
row: 89
|
||||
column: 4
|
||||
end_location:
|
||||
row: 89
|
||||
column: 8
|
||||
row: 90
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: PEP593Test123
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
pub static STRING_QUOTE_PREFIX_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"^(?i)[urb]*['"](?P<raw>.*)['"]$"#).unwrap());
|
||||
|
||||
pub fn is_lower(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
@@ -22,9 +28,22 @@ pub fn is_upper(s: &str) -> bool {
|
||||
cased
|
||||
}
|
||||
|
||||
/// Remove prefixes (u, r, b) and quotes around a string. This expects the given
|
||||
/// string to be a valid Python string representation, it doesn't do any
|
||||
/// validation.
|
||||
pub fn strip_quotes_and_prefixes(s: &str) -> &str {
|
||||
match STRING_QUOTE_PREFIX_REGEX.captures(s) {
|
||||
Some(caps) => match caps.name("raw") {
|
||||
Some(m) => m.as_str(),
|
||||
None => s,
|
||||
},
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::python::string::{is_lower, is_upper};
|
||||
use crate::python::string::{is_lower, is_upper, strip_quotes_and_prefixes};
|
||||
|
||||
#[test]
|
||||
fn test_is_lower() {
|
||||
@@ -47,4 +66,12 @@ mod tests {
|
||||
assert!(!is_upper(""));
|
||||
assert!(!is_upper("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_quotes_and_prefixes() {
|
||||
assert_eq!(strip_quotes_and_prefixes(r#"'a'"#), "a");
|
||||
assert_eq!(strip_quotes_and_prefixes(r#"bur'a'"#), "a");
|
||||
assert_eq!(strip_quotes_and_prefixes(r#"UrB'a'"#), "a");
|
||||
assert_eq!(strip_quotes_and_prefixes(r#""a""#), "a");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
|
||||
use crate::cli::{collect_per_file_ignores, Overrides};
|
||||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
|
||||
use crate::{
|
||||
@@ -185,4 +186,55 @@ impl Configuration {
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, overrides: Overrides) {
|
||||
if let Some(dummy_variable_rgx) = overrides.dummy_variable_rgx {
|
||||
self.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
if let Some(exclude) = overrides.exclude {
|
||||
self.exclude = exclude;
|
||||
}
|
||||
if let Some(extend_exclude) = overrides.extend_exclude {
|
||||
self.extend_exclude = extend_exclude;
|
||||
}
|
||||
if let Some(extend_ignore) = overrides.extend_ignore {
|
||||
self.extend_ignore = extend_ignore;
|
||||
}
|
||||
if let Some(extend_select) = overrides.extend_select {
|
||||
self.extend_select = extend_select;
|
||||
}
|
||||
if let Some(fix) = overrides.fix {
|
||||
self.fix = fix;
|
||||
}
|
||||
if let Some(fixable) = overrides.fixable {
|
||||
self.fixable = fixable;
|
||||
}
|
||||
if let Some(format) = overrides.format {
|
||||
self.format = format;
|
||||
}
|
||||
if let Some(ignore) = overrides.ignore {
|
||||
self.ignore = ignore;
|
||||
}
|
||||
if let Some(line_length) = overrides.line_length {
|
||||
self.line_length = line_length;
|
||||
}
|
||||
if let Some(max_complexity) = overrides.max_complexity {
|
||||
self.mccabe.max_complexity = max_complexity;
|
||||
}
|
||||
if let Some(per_file_ignores) = overrides.per_file_ignores {
|
||||
self.per_file_ignores = collect_per_file_ignores(per_file_ignores);
|
||||
}
|
||||
if let Some(select) = overrides.select {
|
||||
self.select = select;
|
||||
}
|
||||
if let Some(show_source) = overrides.show_source {
|
||||
self.show_source = show_source;
|
||||
}
|
||||
if let Some(target_version) = overrides.target_version {
|
||||
self.target_version = target_version;
|
||||
}
|
||||
if let Some(unfixable) = overrides.unfixable {
|
||||
self.unfixable = unfixable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use rustc_hash::FxHashSet;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::{CheckCodePrefix, SuffixLength};
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes,
|
||||
flake8_tidy_imports, fs, isort, mccabe, pep8_naming, pyupgrade,
|
||||
@@ -36,7 +36,6 @@ pub struct Settings {
|
||||
pub extend_exclude: GlobSet,
|
||||
pub external: FxHashSet<String>,
|
||||
pub fixable: FxHashSet<CheckCode>,
|
||||
pub format: SerializationFormat,
|
||||
pub ignore_init_module_imports: bool,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, FxHashSet<CheckCode>)>,
|
||||
@@ -79,7 +78,6 @@ impl Settings {
|
||||
extend_exclude: resolve_globset(config.extend_exclude, project_root)?,
|
||||
external: FxHashSet::from_iter(config.external),
|
||||
fixable: resolve_codes(&config.fixable, &config.unfixable),
|
||||
format: config.format,
|
||||
flake8_annotations: config.flake8_annotations,
|
||||
flake8_bugbear: config.flake8_bugbear,
|
||||
flake8_import_conventions: config.flake8_import_conventions,
|
||||
@@ -107,7 +105,6 @@ impl Settings {
|
||||
extend_exclude: GlobSet::empty(),
|
||||
external: FxHashSet::default(),
|
||||
fixable: FxHashSet::from_iter([check_code]),
|
||||
format: SerializationFormat::Text,
|
||||
ignore_init_module_imports: false,
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
@@ -135,7 +132,6 @@ impl Settings {
|
||||
extend_exclude: GlobSet::empty(),
|
||||
external: FxHashSet::default(),
|
||||
fixable: FxHashSet::from_iter(check_codes),
|
||||
format: SerializationFormat::Text,
|
||||
ignore_init_module_imports: false,
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct Options {
|
||||
`RUF002`, and `RUF003`.
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<char>",
|
||||
value_type = "Vec<a test>",
|
||||
example = r#"
|
||||
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217),
|
||||
# which could be confused for "-", "p", and "*", respectively.
|
||||
|
||||
Reference in New Issue
Block a user