Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c86e52193c | ||
|
|
efdc4e801d | ||
|
|
f8f2eeed35 | ||
|
|
8fa414b67e | ||
|
|
484d7a30bd | ||
|
|
fb681c614a | ||
|
|
06ed125771 | ||
|
|
74668915b0 | ||
|
|
6da3de25ba | ||
|
|
63b3e00c97 | ||
|
|
39440aa274 | ||
|
|
add96d3dc5 | ||
|
|
6f8e0224d0 | ||
|
|
b8bbafd85b | ||
|
|
40b54d3e8c | ||
|
|
2b44941d63 | ||
|
|
257bd7f1d7 | ||
|
|
5728dceef0 | ||
|
|
6739602806 | ||
|
|
305326f7d7 | ||
|
|
69866f5461 | ||
|
|
41ca29c4f4 |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.172
|
||||
rev: v0.0.175
|
||||
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.172-dev.0"
|
||||
version = "0.0.175-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1821,7 +1821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.172"
|
||||
version = "0.0.175"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1874,7 +1874,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.172"
|
||||
version = "0.0.175"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1892,7 +1892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.172"
|
||||
version = "0.0.175"
|
||||
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.172"
|
||||
version = "0.0.175"
|
||||
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.172", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.175", 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"] }
|
||||
|
||||
18
README.md
18
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).
|
||||
|
||||
@@ -120,12 +121,18 @@ For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff
|
||||
brew install ruff
|
||||
```
|
||||
|
||||
For Conda users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
|
||||
For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
|
||||
|
||||
```shell
|
||||
conda install -c conda-forge ruff
|
||||
```
|
||||
|
||||
For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/community/x86_64/ruff/) on the official repositories:
|
||||
|
||||
```shell
|
||||
pacman -S ruff
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To run Ruff, try any of the following:
|
||||
@@ -147,7 +154,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.172
|
||||
rev: v0.0.175
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -412,14 +419,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 | |
|
||||
@@ -809,12 +816,15 @@ 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 | |
|
||||
| PLR0402 | ConsiderUsingFromImport | Use `from ... import ...` in lieu of alias | |
|
||||
| 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.172"
|
||||
version = "0.0.175"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.172"
|
||||
version = "0.0.175"
|
||||
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.172-dev.0"
|
||||
version = "0.0.175-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
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"
|
||||
|
||||
32
resources/test/fixtures/pylint/global_variable_not_assigned.py
vendored
Normal file
32
resources/test/fixtures/pylint/global_variable_not_assigned.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
###
|
||||
# Errors.
|
||||
###
|
||||
def f():
|
||||
global x
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
def f():
|
||||
global x
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
(x, y) = (1, 2)
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
del x
|
||||
19
resources/test/fixtures/pylint/nonlocal_without_binding.py
vendored
Normal file
19
resources/test/fixtures/pylint/nonlocal_without_binding.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
nonlocal x
|
||||
|
||||
|
||||
def f():
|
||||
nonlocal x
|
||||
|
||||
|
||||
def f():
|
||||
nonlocal y
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
nonlocal x
|
||||
|
||||
def f():
|
||||
nonlocal y
|
||||
148
resources/test/fixtures/pylint/used_prior_global_declaration.py
vendored
Normal file
148
resources/test/fixtures/pylint/used_prior_global_declaration.py
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
###
|
||||
# Errors.
|
||||
###
|
||||
def f():
|
||||
print(x)
|
||||
|
||||
global x
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
print(x)
|
||||
|
||||
global x
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
print(x)
|
||||
|
||||
global x, y
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
print(x)
|
||||
|
||||
global x, y
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
global x
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
x = 1
|
||||
|
||||
global x
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
del x
|
||||
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
del x
|
||||
|
||||
global x
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
del x
|
||||
|
||||
global x
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
del x
|
||||
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
def f():
|
||||
global x
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
|
||||
del x
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.172"
|
||||
version = "0.0.175"
|
||||
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.172"
|
||||
version = "0.0.175"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::{Cmpop, Located};
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::types::{Binding, BindingKind, Scope};
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Vec<String> {
|
||||
@@ -69,6 +72,38 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Ve
|
||||
names
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GlobalVisitor<'a> {
|
||||
globals: FxHashMap<&'a str, &'a Stmt>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for GlobalVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } => {
|
||||
for name in names {
|
||||
self.globals.insert(name, stmt);
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
| StmtKind::ClassDef { .. } => {
|
||||
// Don't recurse.
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a map from global name to its last-defining `Stmt`.
|
||||
pub fn extract_globals(body: &[Stmt]) -> FxHashMap<&str, &Stmt> {
|
||||
let mut visitor = GlobalVisitor::default();
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
visitor.globals
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
|
||||
@@ -32,23 +32,29 @@ impl Range {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionDef<'a> {
|
||||
// Properties derived from StmtKind::FunctionDef.
|
||||
pub name: &'a str,
|
||||
pub args: &'a Arguments,
|
||||
pub body: &'a [Stmt],
|
||||
pub decorator_list: &'a [Expr],
|
||||
// pub returns: Option<&'a Expr>,
|
||||
// pub type_comment: Option<&'a str>,
|
||||
// Scope-specific properties.
|
||||
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
|
||||
pub async_: bool,
|
||||
pub globals: FxHashMap<&'a str, &'a Stmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClassDef<'a> {
|
||||
// Properties derived from StmtKind::ClassDef.
|
||||
pub name: &'a str,
|
||||
pub bases: &'a [Expr],
|
||||
pub keywords: &'a [Keyword],
|
||||
// pub body: &'a [Stmt],
|
||||
pub decorator_list: &'a [Expr],
|
||||
// Scope-specific properties.
|
||||
pub globals: FxHashMap<&'a str, &'a Stmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -73,7 +79,11 @@ pub struct Scope<'a> {
|
||||
pub kind: ScopeKind<'a>,
|
||||
pub import_starred: bool,
|
||||
pub uses_locals: bool,
|
||||
/// A map from bound name to binding index.
|
||||
pub values: FxHashMap<&'a str, usize>,
|
||||
/// A list of (name, index) pairs for bindings that were overridden in the
|
||||
/// scope.
|
||||
pub overridden: Vec<(&'a str, usize)>,
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
@@ -84,6 +94,7 @@ impl<'a> Scope<'a> {
|
||||
import_starred: false,
|
||||
uses_locals: false,
|
||||
values: FxHashMap::default(),
|
||||
overridden: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,8 +128,6 @@ pub struct Binding<'a> {
|
||||
/// Tuple of (scope index, range) indicating the scope and range at which
|
||||
/// the binding was last used.
|
||||
pub used: Option<(usize, Range)>,
|
||||
/// A list of pointers to `Binding` instances that redefined this binding.
|
||||
pub redefined: Vec<usize>,
|
||||
}
|
||||
|
||||
// Pyflakes defines the following binding hierarchy (via inheritance):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::Lines;
|
||||
|
||||
use rustpython_ast::{Located, Location};
|
||||
@@ -5,31 +6,26 @@ use rustpython_ast::{Located, Location};
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
|
||||
let range = Range::from_located(located);
|
||||
checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract the leading words from a line of text.
|
||||
pub fn leading_words(line: &str) -> String {
|
||||
line.trim()
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
|
||||
.collect()
|
||||
pub fn leading_words(line: &str) -> &str {
|
||||
let line = line.trim();
|
||||
line.find(|char: char| !char.is_alphanumeric() && !char.is_whitespace())
|
||||
.map_or(line, |index| &line[..index])
|
||||
}
|
||||
|
||||
/// Extract the leading whitespace from a line of text.
|
||||
pub fn leading_space(line: &str) -> String {
|
||||
line.chars()
|
||||
.take_while(|char| char.is_whitespace())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
|
||||
let range = Range::from_located(located);
|
||||
checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
.to_string()
|
||||
pub fn leading_space(line: &str) -> &str {
|
||||
line.find(|char: char| !char.is_whitespace())
|
||||
.map_or(line, |index| &line[..index])
|
||||
}
|
||||
|
||||
/// Replace any non-whitespace characters from an indentation string.
|
||||
|
||||
650
src/check_ast.rs
650
src/check_ast.rs
@@ -4,7 +4,9 @@ use std::path::Path;
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -22,7 +24,7 @@ use crate::ast::types::{
|
||||
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, Documentable};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
@@ -67,6 +69,7 @@ pub struct Checker<'a> {
|
||||
pub(crate) depths: FxHashMap<RefEquality<'a, Stmt>, usize>,
|
||||
pub(crate) child_to_parent: FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
|
||||
pub(crate) bindings: Vec<Binding<'a>>,
|
||||
pub(crate) redefinitions: IntMap<usize, Vec<usize>>,
|
||||
scopes: Vec<Scope<'a>>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
@@ -74,7 +77,7 @@ pub struct Checker<'a> {
|
||||
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>,
|
||||
deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>,
|
||||
deferred_assignments: Vec<(usize, DeferralContext<'a>)>,
|
||||
deferred_assignments: Vec<DeferralContext<'a>>,
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope,
|
||||
in_f_string: Option<Range>,
|
||||
@@ -113,6 +116,7 @@ impl<'a> Checker<'a> {
|
||||
depths: FxHashMap::default(),
|
||||
child_to_parent: FxHashMap::default(),
|
||||
bindings: vec![],
|
||||
redefinitions: IntMap::default(),
|
||||
scopes: vec![],
|
||||
scope_stack: vec![],
|
||||
dead_scopes: vec![],
|
||||
@@ -237,24 +241,6 @@ where
|
||||
StmtKind::Global { names } => {
|
||||
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
||||
if scope_index != GLOBAL_SCOPE_INDEX {
|
||||
// If the binding doesn't already exist in the global scope, add it.
|
||||
for name in names {
|
||||
if !self.scopes[GLOBAL_SCOPE_INDEX]
|
||||
.values
|
||||
.contains_key(&name.as_str())
|
||||
{
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: None,
|
||||
redefined: vec![],
|
||||
});
|
||||
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the binding to the current scope.
|
||||
let scope = &mut self.scopes[scope_index];
|
||||
let usage = Some((scope.id, Range::from_located(stmt)));
|
||||
@@ -264,8 +250,7 @@ where
|
||||
kind: BindingKind::Global,
|
||||
used: usage,
|
||||
range: Range::from_located(stmt),
|
||||
source: None,
|
||||
redefined: vec![],
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
scope.values.insert(name, index);
|
||||
}
|
||||
@@ -295,8 +280,7 @@ where
|
||||
kind: BindingKind::Nonlocal,
|
||||
used: usage,
|
||||
range: Range::from_located(stmt),
|
||||
source: None,
|
||||
redefined: vec![],
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
scope.values.insert(name, index);
|
||||
}
|
||||
@@ -304,11 +288,23 @@ where
|
||||
// Mark the binding in the defining scopes as used too. (Skip the global scope
|
||||
// and the current scope.)
|
||||
for name in names {
|
||||
let mut exists = false;
|
||||
for index in self.scope_stack.iter().skip(1).rev().skip(1) {
|
||||
if let Some(index) = self.scopes[*index].values.get(&name.as_str()) {
|
||||
exists = true;
|
||||
self.bindings[*index].used = usage;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that every nonlocal has an existing binding from a parent scope.
|
||||
if !exists {
|
||||
if self.settings.enabled.contains(&CheckCode::PLE0117) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::NonlocalWithoutBinding(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +508,6 @@ where
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -589,12 +584,6 @@ where
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
})));
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
@@ -622,7 +611,6 @@ where
|
||||
used: None,
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -663,7 +651,6 @@ where
|
||||
},
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -808,7 +795,6 @@ where
|
||||
)),
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -840,7 +826,6 @@ where
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -910,7 +895,6 @@ where
|
||||
},
|
||||
range,
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1140,7 +1124,20 @@ where
|
||||
// Recurse.
|
||||
let prev_visible_scope = self.visible_scope.clone();
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
|
||||
StmtKind::FunctionDef {
|
||||
body,
|
||||
name,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
body,
|
||||
name,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::B021) {
|
||||
flake8_bugbear::plugins::f_string_docstring(self, body);
|
||||
}
|
||||
@@ -1155,13 +1152,44 @@ where
|
||||
.push((definition, scope.visibility.clone()));
|
||||
self.visible_scope = scope;
|
||||
|
||||
// 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) {
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
|
||||
}
|
||||
}
|
||||
|
||||
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
|
||||
globals,
|
||||
})));
|
||||
|
||||
self.deferred_functions.push((
|
||||
stmt,
|
||||
(self.scope_stack.clone(), self.parents.clone()),
|
||||
self.visible_scope.clone(),
|
||||
));
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
StmtKind::ClassDef {
|
||||
body,
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::B021) {
|
||||
flake8_bugbear::plugins::f_string_docstring(self, body);
|
||||
}
|
||||
@@ -1176,6 +1204,29 @@ where
|
||||
.push((definition, scope.visibility.clone()));
|
||||
self.visible_scope = scope;
|
||||
|
||||
// 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) {
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
|
||||
}
|
||||
}
|
||||
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
globals,
|
||||
})));
|
||||
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
@@ -1227,19 +1278,24 @@ where
|
||||
self.visible_scope = prev_visible_scope;
|
||||
|
||||
// Post-visit.
|
||||
if let StmtKind::ClassDef { name, .. } = &stmt.node {
|
||||
self.pop_scope();
|
||||
self.add_binding(
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
};
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
self.pop_scope();
|
||||
}
|
||||
StmtKind::ClassDef { name, .. } => {
|
||||
self.pop_scope();
|
||||
self.add_binding(
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.pop_parent();
|
||||
}
|
||||
@@ -1369,6 +1425,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PLE0118) {
|
||||
pylint::plugins::used_prior_global_declaration(self, id, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
@@ -1421,41 +1481,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1906,67 +1953,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2497,7 +2516,6 @@ where
|
||||
used: None,
|
||||
range: Range::from_located(arg),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2563,7 +2581,6 @@ impl<'a> Checker<'a> {
|
||||
range: Range::default(),
|
||||
used: None,
|
||||
source: None,
|
||||
redefined: vec![],
|
||||
});
|
||||
scope.values.insert(builtin, index);
|
||||
}
|
||||
@@ -2592,8 +2609,9 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let index = self.bindings.len();
|
||||
let binding_index = self.bindings.len();
|
||||
|
||||
let mut overridden = None;
|
||||
if let Some((stack_index, scope_index)) = self
|
||||
.scope_stack
|
||||
.iter()
|
||||
@@ -2601,8 +2619,8 @@ impl<'a> Checker<'a> {
|
||||
.enumerate()
|
||||
.find(|(_, scope_index)| self.scopes[**scope_index].values.contains_key(&name))
|
||||
{
|
||||
let existing_index = self.scopes[*scope_index].values.get(&name).unwrap();
|
||||
let existing = &self.bindings[*existing_index];
|
||||
let existing_binding_index = self.scopes[*scope_index].values.get(&name).unwrap();
|
||||
let existing = &self.bindings[*existing_binding_index];
|
||||
let in_current_scope = stack_index == 0;
|
||||
if !matches!(existing.kind, BindingKind::Builtin)
|
||||
&& existing.source.as_ref().map_or(true, |left| {
|
||||
@@ -2625,6 +2643,7 @@ impl<'a> Checker<'a> {
|
||||
| BindingKind::FutureImportation
|
||||
);
|
||||
if matches!(binding.kind, BindingKind::LoopVar) && existing_is_import {
|
||||
overridden = Some((*scope_index, *existing_binding_index));
|
||||
if self.settings.enabled.contains(&CheckCode::F402) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::ImportShadowedByLoopVar(
|
||||
@@ -2644,6 +2663,7 @@ impl<'a> Checker<'a> {
|
||||
cast::decorator_list(existing.source.as_ref().unwrap().0),
|
||||
))
|
||||
{
|
||||
overridden = Some((*scope_index, *existing_binding_index));
|
||||
if self.settings.enabled.contains(&CheckCode::F811) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::RedefinedWhileUnused(
|
||||
@@ -2655,11 +2675,21 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
} else if existing_is_import && binding.redefines(existing) {
|
||||
self.bindings[*existing_index].redefined.push(index);
|
||||
self.redefinitions
|
||||
.entry(*existing_binding_index)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(binding_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're about to lose the binding, store it as overriden.
|
||||
if let Some((scope_index, binding_index)) = overridden {
|
||||
self.scopes[scope_index]
|
||||
.overridden
|
||||
.push((name, binding_index));
|
||||
}
|
||||
|
||||
// Assume the rebound name is used as a global or within a loop.
|
||||
let scope = self.current_scope();
|
||||
let binding = match scope.values.get(&name) {
|
||||
@@ -2674,7 +2704,7 @@ impl<'a> Checker<'a> {
|
||||
// in scope.
|
||||
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found"))];
|
||||
if !(matches!(binding.kind, BindingKind::Annotation) && scope.values.contains_key(name)) {
|
||||
scope.values.insert(name, index);
|
||||
scope.values.insert(name, binding_index);
|
||||
}
|
||||
|
||||
self.bindings.push(binding);
|
||||
@@ -2849,7 +2879,6 @@ impl<'a> Checker<'a> {
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -2867,7 +2896,6 @@ impl<'a> Checker<'a> {
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -2881,7 +2909,6 @@ impl<'a> Checker<'a> {
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -2932,7 +2959,6 @@ impl<'a> Checker<'a> {
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -2946,7 +2972,6 @@ impl<'a> Checker<'a> {
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
redefined: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -3054,27 +3079,8 @@ impl<'a> Checker<'a> {
|
||||
self.visible_scope = visibility;
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
|
||||
})));
|
||||
StmtKind::FunctionDef { body, args, .. }
|
||||
| StmtKind::AsyncFunctionDef { body, args, .. } => {
|
||||
self.visit_arguments(args);
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
@@ -3083,12 +3089,7 @@ impl<'a> Checker<'a> {
|
||||
_ => unreachable!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
|
||||
}
|
||||
|
||||
self.deferred_assignments.push((
|
||||
*self.scope_stack.last().expect("No current scope found"),
|
||||
(scopes, parents),
|
||||
));
|
||||
|
||||
self.pop_scope();
|
||||
self.deferred_assignments.push((scopes, parents));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3099,29 +3100,25 @@ impl<'a> Checker<'a> {
|
||||
self.parents = parents.clone();
|
||||
|
||||
if let ExprKind::Lambda { args, body } = &expr.node {
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
|
||||
self.visit_arguments(args);
|
||||
self.visit_expr(body);
|
||||
} else {
|
||||
unreachable!("Expected ExprKind::Lambda");
|
||||
}
|
||||
|
||||
self.deferred_assignments.push((
|
||||
*self.scope_stack.last().expect("No current scope found"),
|
||||
(scopes, parents),
|
||||
));
|
||||
|
||||
self.pop_scope();
|
||||
self.deferred_assignments.push((scopes, parents));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
self.deferred_assignments.reverse();
|
||||
while let Some((index, (scopes, _parents))) = self.deferred_assignments.pop() {
|
||||
while let Some((scopes, _parents)) = self.deferred_assignments.pop() {
|
||||
let scope_index = scopes[scopes.len() - 1];
|
||||
let parent_scope_index = scopes[scopes.len() - 2];
|
||||
if self.settings.enabled.contains(&CheckCode::F841) {
|
||||
self.add_checks(
|
||||
pyflakes::checks::unused_variable(
|
||||
&self.scopes[index],
|
||||
&self.scopes[scope_index],
|
||||
&self.bindings,
|
||||
&self.settings.dummy_variable_rgx,
|
||||
)
|
||||
@@ -3131,7 +3128,7 @@ impl<'a> Checker<'a> {
|
||||
if self.settings.enabled.contains(&CheckCode::F842) {
|
||||
self.add_checks(
|
||||
pyflakes::checks::unused_annotation(
|
||||
&self.scopes[index],
|
||||
&self.scopes[scope_index],
|
||||
&self.bindings,
|
||||
&self.settings.dummy_variable_rgx,
|
||||
)
|
||||
@@ -3147,10 +3144,8 @@ impl<'a> Checker<'a> {
|
||||
self.add_checks(
|
||||
flake8_unused_arguments::plugins::unused_arguments(
|
||||
self,
|
||||
&self.scopes[*scopes
|
||||
.last()
|
||||
.expect("Expected parent scope above function scope")],
|
||||
&self.scopes[index],
|
||||
&self.scopes[parent_scope_index],
|
||||
&self.scopes[scope_index],
|
||||
&self.bindings,
|
||||
)
|
||||
.into_iter(),
|
||||
@@ -3164,6 +3159,7 @@ impl<'a> Checker<'a> {
|
||||
&& !self.settings.enabled.contains(&CheckCode::F405)
|
||||
&& !self.settings.enabled.contains(&CheckCode::F811)
|
||||
&& !self.settings.enabled.contains(&CheckCode::F822)
|
||||
&& !self.settings.enabled.contains(&CheckCode::PLW0602)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -3175,6 +3171,24 @@ impl<'a> Checker<'a> {
|
||||
.rev()
|
||||
.map(|index| &self.scopes[*index])
|
||||
{
|
||||
// PLW0602
|
||||
if self.settings.enabled.contains(&CheckCode::PLW0602) {
|
||||
for (name, index) in &scope.values {
|
||||
let binding = &self.bindings[*index];
|
||||
if matches!(binding.kind, BindingKind::Global) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::GlobalVariableNotAssigned((*name).to_string()),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Imports in classes are public members.
|
||||
if matches!(scope.kind, ScopeKind::Class(..)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let all_binding: Option<&Binding> = scope
|
||||
.values
|
||||
.get("__all__")
|
||||
@@ -3202,6 +3216,9 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any bindings that were redefined in another scope, and remain
|
||||
// unused. Note that we only store references in `redefinitions` if
|
||||
// the bindings are in different scopes.
|
||||
if self.settings.enabled.contains(&CheckCode::F811) {
|
||||
for (name, index) in &scope.values {
|
||||
let binding = &self.bindings[*index];
|
||||
@@ -3224,14 +3241,16 @@ impl<'a> Checker<'a> {
|
||||
continue;
|
||||
}
|
||||
|
||||
for index in &binding.redefined {
|
||||
checks.push(Check::new(
|
||||
CheckKind::RedefinedWhileUnused(
|
||||
(*name).to_string(),
|
||||
binding.range.location.row(),
|
||||
),
|
||||
self.bindings[*index].range,
|
||||
));
|
||||
if let Some(indices) = self.redefinitions.get(index) {
|
||||
for index in indices {
|
||||
checks.push(Check::new(
|
||||
CheckKind::RedefinedWhileUnused(
|
||||
(*name).to_string(),
|
||||
binding.range.location.row(),
|
||||
),
|
||||
self.bindings[*index].range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3278,7 +3297,11 @@ impl<'a> Checker<'a> {
|
||||
|
||||
let mut unused: FxHashMap<BindingContext, Vec<UnusedImport>> = FxHashMap::default();
|
||||
|
||||
for (name, index) in &scope.values {
|
||||
for (name, index) in scope
|
||||
.values
|
||||
.iter()
|
||||
.chain(scope.overridden.iter().map(|(a, b)| (a, b)))
|
||||
{
|
||||
let binding = &self.bindings[*index];
|
||||
|
||||
let (BindingKind::Importation(_, full_name)
|
||||
@@ -3354,22 +3377,68 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_definitions(&mut self) {
|
||||
let enforce_annotations = self.settings.enabled.contains(&CheckCode::ANN001)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN002)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN003)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN101)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN102)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN201)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN202)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN204)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN205)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN206)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN401);
|
||||
let enforce_docstrings = self.settings.enabled.contains(&CheckCode::D100)
|
||||
|| self.settings.enabled.contains(&CheckCode::D101)
|
||||
|| self.settings.enabled.contains(&CheckCode::D102)
|
||||
|| self.settings.enabled.contains(&CheckCode::D103)
|
||||
|| self.settings.enabled.contains(&CheckCode::D104)
|
||||
|| self.settings.enabled.contains(&CheckCode::D105)
|
||||
|| self.settings.enabled.contains(&CheckCode::D106)
|
||||
|| self.settings.enabled.contains(&CheckCode::D107)
|
||||
|| self.settings.enabled.contains(&CheckCode::D200)
|
||||
|| self.settings.enabled.contains(&CheckCode::D201)
|
||||
|| self.settings.enabled.contains(&CheckCode::D202)
|
||||
|| self.settings.enabled.contains(&CheckCode::D203)
|
||||
|| self.settings.enabled.contains(&CheckCode::D204)
|
||||
|| self.settings.enabled.contains(&CheckCode::D205)
|
||||
|| self.settings.enabled.contains(&CheckCode::D206)
|
||||
|| self.settings.enabled.contains(&CheckCode::D207)
|
||||
|| self.settings.enabled.contains(&CheckCode::D208)
|
||||
|| self.settings.enabled.contains(&CheckCode::D209)
|
||||
|| self.settings.enabled.contains(&CheckCode::D210)
|
||||
|| self.settings.enabled.contains(&CheckCode::D211)
|
||||
|| self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D213)
|
||||
|| self.settings.enabled.contains(&CheckCode::D214)
|
||||
|| self.settings.enabled.contains(&CheckCode::D215)
|
||||
|| self.settings.enabled.contains(&CheckCode::D300)
|
||||
|| self.settings.enabled.contains(&CheckCode::D301)
|
||||
|| self.settings.enabled.contains(&CheckCode::D400)
|
||||
|| self.settings.enabled.contains(&CheckCode::D402)
|
||||
|| self.settings.enabled.contains(&CheckCode::D403)
|
||||
|| self.settings.enabled.contains(&CheckCode::D404)
|
||||
|| self.settings.enabled.contains(&CheckCode::D405)
|
||||
|| self.settings.enabled.contains(&CheckCode::D406)
|
||||
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||
|| self.settings.enabled.contains(&CheckCode::D408)
|
||||
|| self.settings.enabled.contains(&CheckCode::D409)
|
||||
|| self.settings.enabled.contains(&CheckCode::D410)
|
||||
|| self.settings.enabled.contains(&CheckCode::D411)
|
||||
|| self.settings.enabled.contains(&CheckCode::D412)
|
||||
|| self.settings.enabled.contains(&CheckCode::D413)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D415)
|
||||
|| self.settings.enabled.contains(&CheckCode::D416)
|
||||
|| self.settings.enabled.contains(&CheckCode::D417)
|
||||
|| self.settings.enabled.contains(&CheckCode::D418)
|
||||
|| self.settings.enabled.contains(&CheckCode::D419);
|
||||
|
||||
let mut overloaded_name: Option<String> = None;
|
||||
self.definitions.reverse();
|
||||
while let Some((definition, visibility)) = self.definitions.pop() {
|
||||
// flake8-annotations
|
||||
if self.settings.enabled.contains(&CheckCode::ANN001)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN002)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN003)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN101)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN102)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN201)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN202)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN204)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN205)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN206)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN401)
|
||||
{
|
||||
if enforce_annotations {
|
||||
// TODO(charlie): This should be even stricter, in that an overload
|
||||
// implementation should come immediately after the overloaded
|
||||
// interfaces, without any AST nodes in between. Right now, we
|
||||
@@ -3388,87 +3457,110 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
// pydocstyle
|
||||
if !pydocstyle::plugins::not_empty(self, &definition) {
|
||||
continue;
|
||||
}
|
||||
if !pydocstyle::plugins::not_missing(self, &definition, &visibility) {
|
||||
continue;
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D200) {
|
||||
pydocstyle::plugins::one_liner(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D201)
|
||||
|| self.settings.enabled.contains(&CheckCode::D202)
|
||||
{
|
||||
pydocstyle::plugins::blank_before_after_function(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D203)
|
||||
|| self.settings.enabled.contains(&CheckCode::D204)
|
||||
|| self.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
pydocstyle::plugins::blank_before_after_class(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D205) {
|
||||
pydocstyle::plugins::blank_after_summary(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D206)
|
||||
|| self.settings.enabled.contains(&CheckCode::D207)
|
||||
|| self.settings.enabled.contains(&CheckCode::D208)
|
||||
{
|
||||
pydocstyle::plugins::indent(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D209) {
|
||||
pydocstyle::plugins::newline_after_last_paragraph(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D210) {
|
||||
pydocstyle::plugins::no_surrounding_whitespace(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D213)
|
||||
{
|
||||
pydocstyle::plugins::multi_line_summary_start(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D300) {
|
||||
pydocstyle::plugins::triple_quotes(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D301) {
|
||||
pydocstyle::plugins::backslashes(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D400) {
|
||||
pydocstyle::plugins::ends_with_period(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D402) {
|
||||
pydocstyle::plugins::no_signature(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D403) {
|
||||
pydocstyle::plugins::capitalized(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D404) {
|
||||
pydocstyle::plugins::starts_with_this(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D415) {
|
||||
pydocstyle::plugins::ends_with_punctuation(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D418) {
|
||||
pydocstyle::plugins::if_needed(self, &definition);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D214)
|
||||
|| self.settings.enabled.contains(&CheckCode::D215)
|
||||
|| self.settings.enabled.contains(&CheckCode::D405)
|
||||
|| self.settings.enabled.contains(&CheckCode::D406)
|
||||
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||
|| self.settings.enabled.contains(&CheckCode::D408)
|
||||
|| self.settings.enabled.contains(&CheckCode::D409)
|
||||
|| self.settings.enabled.contains(&CheckCode::D410)
|
||||
|| self.settings.enabled.contains(&CheckCode::D411)
|
||||
|| self.settings.enabled.contains(&CheckCode::D412)
|
||||
|| self.settings.enabled.contains(&CheckCode::D413)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D416)
|
||||
|| self.settings.enabled.contains(&CheckCode::D417)
|
||||
{
|
||||
pydocstyle::plugins::sections(self, &definition);
|
||||
if enforce_docstrings {
|
||||
if definition.docstring.is_none() {
|
||||
pydocstyle::plugins::not_missing(self, &definition, &visibility);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract a `Docstring` from a `Definition`.
|
||||
let expr = definition.docstring.unwrap();
|
||||
let content = self
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(expr));
|
||||
let indentation = self.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(expr.location.row(), 0),
|
||||
end_location: Location::new(expr.location.row(), expr.location.column()),
|
||||
});
|
||||
let body = pydocstyle::helpers::raw_contents(&content);
|
||||
let docstring = Docstring {
|
||||
kind: definition.kind,
|
||||
expr,
|
||||
contents: &content,
|
||||
indentation: &indentation,
|
||||
body,
|
||||
};
|
||||
|
||||
if !pydocstyle::plugins::not_empty(self, &docstring) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::D200) {
|
||||
pydocstyle::plugins::one_liner(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D201)
|
||||
|| self.settings.enabled.contains(&CheckCode::D202)
|
||||
{
|
||||
pydocstyle::plugins::blank_before_after_function(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D203)
|
||||
|| self.settings.enabled.contains(&CheckCode::D204)
|
||||
|| self.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
pydocstyle::plugins::blank_before_after_class(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D205) {
|
||||
pydocstyle::plugins::blank_after_summary(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D206)
|
||||
|| self.settings.enabled.contains(&CheckCode::D207)
|
||||
|| self.settings.enabled.contains(&CheckCode::D208)
|
||||
{
|
||||
pydocstyle::plugins::indent(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D209) {
|
||||
pydocstyle::plugins::newline_after_last_paragraph(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D210) {
|
||||
pydocstyle::plugins::no_surrounding_whitespace(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D213)
|
||||
{
|
||||
pydocstyle::plugins::multi_line_summary_start(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D300) {
|
||||
pydocstyle::plugins::triple_quotes(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D301) {
|
||||
pydocstyle::plugins::backslashes(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D400) {
|
||||
pydocstyle::plugins::ends_with_period(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D402) {
|
||||
pydocstyle::plugins::no_signature(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D403) {
|
||||
pydocstyle::plugins::capitalized(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D404) {
|
||||
pydocstyle::plugins::starts_with_this(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D415) {
|
||||
pydocstyle::plugins::ends_with_punctuation(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D418) {
|
||||
pydocstyle::plugins::if_needed(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D214)
|
||||
|| self.settings.enabled.contains(&CheckCode::D215)
|
||||
|| self.settings.enabled.contains(&CheckCode::D405)
|
||||
|| self.settings.enabled.contains(&CheckCode::D406)
|
||||
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||
|| self.settings.enabled.contains(&CheckCode::D408)
|
||||
|| self.settings.enabled.contains(&CheckCode::D409)
|
||||
|| self.settings.enabled.contains(&CheckCode::D410)
|
||||
|| self.settings.enabled.contains(&CheckCode::D411)
|
||||
|| self.settings.enabled.contains(&CheckCode::D412)
|
||||
|| self.settings.enabled.contains(&CheckCode::D413)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D416)
|
||||
|| self.settings.enabled.contains(&CheckCode::D417)
|
||||
{
|
||||
pydocstyle::plugins::sections(self, &docstring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +99,15 @@ pub enum CheckCode {
|
||||
PLC0414,
|
||||
PLC2201,
|
||||
PLC3002,
|
||||
PLE0117,
|
||||
PLE0118,
|
||||
PLE1142,
|
||||
PLR0206,
|
||||
PLR0402,
|
||||
PLR1701,
|
||||
PLR1722,
|
||||
PLW0120,
|
||||
PLW0602,
|
||||
// flake8-builtins
|
||||
A001,
|
||||
A002,
|
||||
@@ -635,15 +638,18 @@ pub enum CheckKind {
|
||||
UnusedVariable(String),
|
||||
YieldOutsideFunction(DeferralKeyword),
|
||||
// pylint
|
||||
ConsiderMergingIsinstance(String, Vec<String>),
|
||||
UselessImportAlias,
|
||||
MisplacedComparisonConstant(String),
|
||||
UnnecessaryDirectLambdaCall,
|
||||
PropertyWithParameters,
|
||||
ConsiderUsingFromImport(String, String),
|
||||
AwaitOutsideAsync,
|
||||
UselessElseOnLoop,
|
||||
ConsiderMergingIsinstance(String, Vec<String>),
|
||||
ConsiderUsingFromImport(String, String),
|
||||
GlobalVariableNotAssigned(String),
|
||||
MisplacedComparisonConstant(String),
|
||||
NonlocalWithoutBinding(String),
|
||||
PropertyWithParameters,
|
||||
UnnecessaryDirectLambdaCall,
|
||||
UseSysExit(String),
|
||||
UsedPriorGlobalDeclaration(String, usize),
|
||||
UselessElseOnLoop,
|
||||
UselessImportAlias,
|
||||
// flake8-builtins
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
@@ -950,6 +956,8 @@ impl CheckCode {
|
||||
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
|
||||
CheckCode::PLC2201 => CheckKind::MisplacedComparisonConstant("...".to_string()),
|
||||
CheckCode::PLC3002 => CheckKind::UnnecessaryDirectLambdaCall,
|
||||
CheckCode::PLE0117 => CheckKind::NonlocalWithoutBinding("...".to_string()),
|
||||
CheckCode::PLE0118 => CheckKind::UsedPriorGlobalDeclaration("...".to_string(), 1),
|
||||
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
|
||||
CheckCode::PLR0402 => {
|
||||
CheckKind::ConsiderUsingFromImport("...".to_string(), "...".to_string())
|
||||
@@ -960,6 +968,7 @@ impl CheckCode {
|
||||
}
|
||||
CheckCode::PLR1722 => CheckKind::UseSysExit("exit".to_string()),
|
||||
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
|
||||
CheckCode::PLW0602 => CheckKind::GlobalVariableNotAssigned("...".to_string()),
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
@@ -1402,12 +1411,15 @@ impl CheckCode {
|
||||
CheckCode::PLC0414 => CheckCategory::Pylint,
|
||||
CheckCode::PLC2201 => CheckCategory::Pylint,
|
||||
CheckCode::PLC3002 => CheckCategory::Pylint,
|
||||
CheckCode::PLE0117 => CheckCategory::Pylint,
|
||||
CheckCode::PLE0118 => CheckCategory::Pylint,
|
||||
CheckCode::PLE1142 => CheckCategory::Pylint,
|
||||
CheckCode::PLR0206 => CheckCategory::Pylint,
|
||||
CheckCode::PLR0402 => CheckCategory::Pylint,
|
||||
CheckCode::PLR1701 => CheckCategory::Pylint,
|
||||
CheckCode::PLR1722 => CheckCategory::Pylint,
|
||||
CheckCode::PLW0120 => CheckCategory::Pylint,
|
||||
CheckCode::PLW0602 => CheckCategory::Pylint,
|
||||
CheckCode::Q000 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q001 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q002 => CheckCategory::Flake8Quotes,
|
||||
@@ -1530,15 +1542,18 @@ impl CheckKind {
|
||||
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
|
||||
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
|
||||
// pylint
|
||||
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
|
||||
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
|
||||
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
|
||||
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
|
||||
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
|
||||
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
|
||||
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
|
||||
CheckKind::GlobalVariableNotAssigned(..) => &CheckCode::PLW0602,
|
||||
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
|
||||
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
|
||||
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
|
||||
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
|
||||
CheckKind::NonlocalWithoutBinding(..) => &CheckCode::PLE0117,
|
||||
CheckKind::UsedPriorGlobalDeclaration(..) => &CheckCode::PLE0118,
|
||||
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
|
||||
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
@@ -1947,6 +1962,9 @@ impl CheckKind {
|
||||
CheckKind::MisplacedComparisonConstant(comparison) => {
|
||||
format!("Comparison should be {comparison}")
|
||||
}
|
||||
CheckKind::NonlocalWithoutBinding(name) => {
|
||||
format!("Nonlocal name `{name}` found without binding")
|
||||
}
|
||||
CheckKind::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
|
||||
the expression inline instead."
|
||||
.to_string(),
|
||||
@@ -1956,6 +1974,12 @@ impl CheckKind {
|
||||
CheckKind::ConsiderUsingFromImport(module, name) => {
|
||||
format!("Use `from {module} import {name}` in lieu of alias")
|
||||
}
|
||||
CheckKind::UsedPriorGlobalDeclaration(name, line) => {
|
||||
format!("Name `{name}` is used prior to global declaration on line {line}")
|
||||
}
|
||||
CheckKind::GlobalVariableNotAssigned(name) => {
|
||||
format!("Using global for `{name}` but no assignment is done")
|
||||
}
|
||||
CheckKind::AwaitOutsideAsync => {
|
||||
"`await` should be used within an async function".to_string()
|
||||
}
|
||||
@@ -2651,6 +2675,7 @@ impl CheckKind {
|
||||
| CheckKind::NotIsTest
|
||||
| CheckKind::OneBlankLineAfterClass(..)
|
||||
| CheckKind::OneBlankLineBeforeClass(..)
|
||||
| CheckKind::PercentFormatExtraNamedArguments(..)
|
||||
| CheckKind::PEP3120UnnecessaryCodingComment
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
@@ -2663,6 +2688,7 @@ impl CheckKind {
|
||||
| CheckKind::SectionUnderlineMatchesSectionLength(..)
|
||||
| CheckKind::SectionUnderlineNotOverIndented(..)
|
||||
| CheckKind::SetAttrWithConstant
|
||||
| CheckKind::StringDotFormatExtraNamedArguments(..)
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TrueFalseComparison(..)
|
||||
| CheckKind::TypeOfPrimitive(..)
|
||||
|
||||
@@ -315,6 +315,11 @@ pub enum CheckCodePrefix {
|
||||
PLC300,
|
||||
PLC3002,
|
||||
PLE,
|
||||
PLE0,
|
||||
PLE01,
|
||||
PLE011,
|
||||
PLE0117,
|
||||
PLE0118,
|
||||
PLE1,
|
||||
PLE11,
|
||||
PLE114,
|
||||
@@ -338,6 +343,9 @@ pub enum CheckCodePrefix {
|
||||
PLW01,
|
||||
PLW012,
|
||||
PLW0120,
|
||||
PLW06,
|
||||
PLW060,
|
||||
PLW0602,
|
||||
Q,
|
||||
Q0,
|
||||
Q00,
|
||||
@@ -1343,7 +1351,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::PLC30 => vec![CheckCode::PLC3002],
|
||||
CheckCodePrefix::PLC300 => vec![CheckCode::PLC3002],
|
||||
CheckCodePrefix::PLC3002 => vec![CheckCode::PLC3002],
|
||||
CheckCodePrefix::PLE => vec![CheckCode::PLE1142],
|
||||
CheckCodePrefix::PLE => {
|
||||
vec![CheckCode::PLE0117, CheckCode::PLE0118, CheckCode::PLE1142]
|
||||
}
|
||||
CheckCodePrefix::PLE0 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
|
||||
CheckCodePrefix::PLE01 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
|
||||
CheckCodePrefix::PLE011 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
|
||||
CheckCodePrefix::PLE0117 => vec![CheckCode::PLE0117],
|
||||
CheckCodePrefix::PLE0118 => vec![CheckCode::PLE0118],
|
||||
CheckCodePrefix::PLE1 => vec![CheckCode::PLE1142],
|
||||
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
|
||||
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
|
||||
@@ -1367,11 +1382,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
|
||||
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
|
||||
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
|
||||
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
|
||||
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
|
||||
CheckCodePrefix::PLW => vec![CheckCode::PLW0120, CheckCode::PLW0602],
|
||||
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120, CheckCode::PLW0602],
|
||||
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
|
||||
CheckCodePrefix::PLW012 => vec![CheckCode::PLW0120],
|
||||
CheckCodePrefix::PLW0120 => vec![CheckCode::PLW0120],
|
||||
CheckCodePrefix::PLW06 => vec![CheckCode::PLW0602],
|
||||
CheckCodePrefix::PLW060 => vec![CheckCode::PLW0602],
|
||||
CheckCodePrefix::PLW0602 => vec![CheckCode::PLW0602],
|
||||
CheckCodePrefix::Q => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
@@ -2118,6 +2136,11 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::PLC300 => SuffixLength::Three,
|
||||
CheckCodePrefix::PLC3002 => SuffixLength::Four,
|
||||
CheckCodePrefix::PLE => SuffixLength::Zero,
|
||||
CheckCodePrefix::PLE0 => SuffixLength::One,
|
||||
CheckCodePrefix::PLE01 => SuffixLength::Two,
|
||||
CheckCodePrefix::PLE011 => SuffixLength::Three,
|
||||
CheckCodePrefix::PLE0117 => SuffixLength::Four,
|
||||
CheckCodePrefix::PLE0118 => SuffixLength::Four,
|
||||
CheckCodePrefix::PLE1 => SuffixLength::One,
|
||||
CheckCodePrefix::PLE11 => SuffixLength::Two,
|
||||
CheckCodePrefix::PLE114 => SuffixLength::Three,
|
||||
@@ -2141,6 +2164,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::PLW01 => SuffixLength::Two,
|
||||
CheckCodePrefix::PLW012 => SuffixLength::Three,
|
||||
CheckCodePrefix::PLW0120 => SuffixLength::Four,
|
||||
CheckCodePrefix::PLW06 => SuffixLength::Two,
|
||||
CheckCodePrefix::PLW060 => SuffixLength::Three,
|
||||
CheckCodePrefix::PLW0602 => SuffixLength::Four,
|
||||
CheckCodePrefix::Q => SuffixLength::Zero,
|
||||
CheckCodePrefix::Q0 => SuffixLength::One,
|
||||
CheckCodePrefix::Q00 => SuffixLength::Two,
|
||||
|
||||
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 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DefinitionKind<'a> {
|
||||
Module,
|
||||
Package,
|
||||
@@ -17,6 +19,15 @@ pub struct Definition<'a> {
|
||||
pub docstring: Option<&'a Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Docstring<'a> {
|
||||
pub kind: DefinitionKind<'a>,
|
||||
pub expr: &'a Expr,
|
||||
pub contents: &'a Cow<'a, str>,
|
||||
pub body: &'a str,
|
||||
pub indentation: &'a Cow<'a, str>,
|
||||
}
|
||||
|
||||
pub enum Documentable {
|
||||
Class,
|
||||
Function,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::docstrings::styles::SectionStyle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SectionContext<'a> {
|
||||
pub(crate) section_name: String,
|
||||
pub(crate) section_name: &'a str,
|
||||
pub(crate) previous_line: &'a str,
|
||||
pub(crate) line: &'a str,
|
||||
pub(crate) following_lines: &'a [&'a str],
|
||||
@@ -22,7 +22,7 @@ fn is_docstring_section(context: &SectionContext) -> bool {
|
||||
let section_name_suffix = context
|
||||
.line
|
||||
.trim()
|
||||
.strip_prefix(&context.section_name)
|
||||
.strip_prefix(context.section_name)
|
||||
.unwrap()
|
||||
.trim();
|
||||
let this_looks_like_a_section_name =
|
||||
|
||||
@@ -19,14 +19,12 @@ fn extract_range(body: &[&Stmt]) -> Range {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
|
||||
fn extract_indentation_range(body: &[&Stmt]) -> Range {
|
||||
let location = body.first().unwrap().location;
|
||||
let range = Range {
|
||||
Range {
|
||||
location: Location::new(location.row(), 0),
|
||||
end_location: location,
|
||||
};
|
||||
let existing = locator.slice_source_code_range(&range);
|
||||
leading_space(&existing)
|
||||
}
|
||||
}
|
||||
|
||||
/// I001
|
||||
@@ -36,8 +34,10 @@ pub fn check_imports(
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
) -> 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);
|
||||
let indentation = extract_indentation(&block.imports, locator);
|
||||
|
||||
// Extract comments. Take care to grab any inline comments from the last line.
|
||||
let comments = comments::collect_comments(
|
||||
@@ -77,7 +77,7 @@ pub fn check_imports(
|
||||
if has_leading_content {
|
||||
content.push('\n');
|
||||
}
|
||||
content.push_str(&indent(&expected, &indentation));
|
||||
content.push_str(&indent(&expected, indentation));
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
// Preserve leading prefix (but put the imports on a new line).
|
||||
@@ -104,7 +104,7 @@ pub fn check_imports(
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::replacement(
|
||||
indent(&expected, &indentation),
|
||||
indent(&expected, indentation),
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
|
||||
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.
|
||||
|
||||
@@ -298,11 +298,11 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
|
||||
{
|
||||
match function(id, args, body) {
|
||||
Ok(content) => {
|
||||
let indentation =
|
||||
&leading_space(&checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(stmt.location.row(), 0),
|
||||
end_location: Location::new(stmt.location.row() + 1, 0),
|
||||
}));
|
||||
let first_line = checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(stmt.location.row(), 0),
|
||||
end_location: Location::new(stmt.location.row() + 1, 0),
|
||||
});
|
||||
let indentation = &leading_space(&first_line);
|
||||
let mut indented = String::new();
|
||||
for (idx, line) in content.lines().enumerate() {
|
||||
if idx == 0 {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::docstrings::constants;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Strip the leading and trailing quotes from a docstring.
|
||||
pub fn raw_contents(contents: &str) -> &str {
|
||||
@@ -20,12 +16,8 @@ pub fn raw_contents(contents: &str) -> &str {
|
||||
}
|
||||
|
||||
/// Return the leading quote string for a docstring (e.g., `"""`).
|
||||
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
|
||||
if let Some(first_line) = locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
pub fn leading_quote(content: &str) -> Option<&str> {
|
||||
if let Some(first_line) = content.lines().next() {
|
||||
for pattern in constants::TRIPLE_QUOTE_PREFIXES
|
||||
.iter()
|
||||
.chain(constants::SINGLE_QUOTE_PREFIXES)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod helpers;
|
||||
pub mod helpers;
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod cformat;
|
||||
pub mod checks;
|
||||
pub mod fixes;
|
||||
mod foo;
|
||||
pub mod format;
|
||||
pub mod plugins;
|
||||
|
||||
@@ -405,14 +404,23 @@ mod tests {
|
||||
"#,
|
||||
&[],
|
||||
)?;
|
||||
// Pyflakes allows this, but it causes other issues.
|
||||
// flakes(
|
||||
// r#"
|
||||
// def c(): bar
|
||||
// def b(): global bar; bar = 1
|
||||
// "#,
|
||||
// &[],
|
||||
// )?;
|
||||
flakes(
|
||||
r#"
|
||||
def c(): bar
|
||||
def b(): global bar; bar = 1
|
||||
"#,
|
||||
&[],
|
||||
)?;
|
||||
// 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(())
|
||||
}
|
||||
|
||||
@@ -1105,11 +1113,11 @@ mod tests {
|
||||
fn aliased_import() -> Result<()> {
|
||||
flakes(
|
||||
"import fu as FU, bar as FU",
|
||||
&[CheckCode::F811, CheckCode::F401],
|
||||
&[CheckCode::F401, CheckCode::F811, CheckCode::F401],
|
||||
)?;
|
||||
flakes(
|
||||
"from moo import fu as FU, bar as FU",
|
||||
&[CheckCode::F811, CheckCode::F401],
|
||||
&[CheckCode::F401, CheckCode::F811, CheckCode::F401],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1146,9 +1154,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn redefined_while_unused() -> Result<()> {
|
||||
flakes("import fu; fu = 3", &[CheckCode::F811])?;
|
||||
flakes("import fu; fu, bar = 3", &[CheckCode::F811])?;
|
||||
flakes("import fu; [fu, bar] = 3", &[CheckCode::F811])?;
|
||||
flakes("import fu; fu = 3", &[CheckCode::F401, CheckCode::F811])?;
|
||||
flakes(
|
||||
"import fu; fu, bar = 3",
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
flakes(
|
||||
"import fu; [fu, bar] = 3",
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1165,7 +1179,7 @@ mod tests {
|
||||
import os
|
||||
os.path
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1203,7 +1217,7 @@ mod tests {
|
||||
pass
|
||||
os.path
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1279,7 +1293,7 @@ mod tests {
|
||||
from bb import mixer
|
||||
mixer(123)
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1351,7 +1365,7 @@ mod tests {
|
||||
def fu():
|
||||
pass
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1429,7 +1443,7 @@ mod tests {
|
||||
class fu:
|
||||
pass
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1476,7 +1490,7 @@ mod tests {
|
||||
class bar:
|
||||
import fu
|
||||
"#,
|
||||
&[CheckCode::F401],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
flakes(
|
||||
@@ -1486,7 +1500,7 @@ mod tests {
|
||||
|
||||
fu
|
||||
"#,
|
||||
&[CheckCode::F401, CheckCode::F821],
|
||||
&[CheckCode::F821],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1693,7 +1707,7 @@ mod tests {
|
||||
for fu in range(2):
|
||||
pass
|
||||
"#,
|
||||
&[CheckCode::F402],
|
||||
&[CheckCode::F401, CheckCode::F402],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1850,7 +1864,7 @@ mod tests {
|
||||
try: pass
|
||||
except Exception as fu: pass
|
||||
"#,
|
||||
&[CheckCode::F811, CheckCode::F841],
|
||||
&[CheckCode::F401, CheckCode::F811, CheckCode::F841],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -2091,7 +2105,7 @@ mod tests {
|
||||
def fun(self):
|
||||
fu
|
||||
"#,
|
||||
&[CheckCode::F401, CheckCode::F821],
|
||||
&[CheckCode::F821],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -2160,7 +2174,7 @@ mod tests {
|
||||
import fu.bar, fu.bar
|
||||
fu.bar
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
flakes(
|
||||
r#"
|
||||
@@ -2168,7 +2182,7 @@ mod tests {
|
||||
import fu.bar
|
||||
fu.bar
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -2324,7 +2338,7 @@ mod tests {
|
||||
fu
|
||||
fu
|
||||
"#,
|
||||
&[CheckCode::F811],
|
||||
&[CheckCode::F401, CheckCode::F811],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -2337,18 +2351,17 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn imported_in_class() -> Result<()> {
|
||||
// Imports in class scope can be used through self.
|
||||
flakes(
|
||||
r#"
|
||||
class c:
|
||||
class C:
|
||||
import i
|
||||
def __init__(self):
|
||||
self.i
|
||||
"#,
|
||||
&[CheckCode::F401],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
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
|
||||
|
||||
@@ -14,6 +14,8 @@ mod tests {
|
||||
#[test_case(CheckCode::PLC0414, Path::new("import_aliasing.py"); "PLC0414")]
|
||||
#[test_case(CheckCode::PLC2201, Path::new("misplaced_comparison_constant.py"); "PLC2201")]
|
||||
#[test_case(CheckCode::PLC3002, Path::new("unnecessary_direct_lambda_call.py"); "PLC3002")]
|
||||
#[test_case(CheckCode::PLE0117, Path::new("nonlocal_without_binding.py"); "PLE0117")]
|
||||
#[test_case(CheckCode::PLE0118, Path::new("used_prior_global_declaration.py"); "PLE0118")]
|
||||
#[test_case(CheckCode::PLE1142, Path::new("await_outside_async.py"); "PLE1142")]
|
||||
#[test_case(CheckCode::PLR0206, Path::new("property_with_parameters.py"); "PLR0206")]
|
||||
#[test_case(CheckCode::PLR0402, Path::new("import_aliasing.py"); "PLR0402")]
|
||||
@@ -26,6 +28,7 @@ mod tests {
|
||||
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_5.py"); "PLR1722_5")]
|
||||
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_6.py"); "PLR1722_6")]
|
||||
#[test_case(CheckCode::PLW0120, Path::new("useless_else_on_loop.py"); "PLW0120")]
|
||||
#[test_case(CheckCode::PLW0602, Path::new("global_variable_not_assigned.py"); "PLW0602")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
@@ -5,6 +5,7 @@ pub use property_with_parameters::property_with_parameters;
|
||||
pub use unnecessary_direct_lambda_call::unnecessary_direct_lambda_call;
|
||||
pub use use_from_import::use_from_import;
|
||||
pub use use_sys_exit::use_sys_exit;
|
||||
pub use used_prior_global_declaration::used_prior_global_declaration;
|
||||
pub use useless_else_on_loop::useless_else_on_loop;
|
||||
pub use useless_import_alias::useless_import_alias;
|
||||
|
||||
@@ -15,5 +16,6 @@ mod property_with_parameters;
|
||||
mod unnecessary_direct_lambda_call;
|
||||
mod use_from_import;
|
||||
mod use_sys_exit;
|
||||
mod used_prior_global_declaration;
|
||||
mod useless_else_on_loop;
|
||||
mod useless_import_alias;
|
||||
|
||||
23
src/pylint/plugins/used_prior_global_declaration.rs
Normal file
23
src/pylint/plugins/used_prior_global_declaration.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::types::{Range, ScopeKind};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::CheckKind;
|
||||
use crate::Check;
|
||||
|
||||
/// PLE0118
|
||||
pub fn used_prior_global_declaration(checker: &mut Checker, name: &str, expr: &Expr) {
|
||||
let globals = match &checker.current_scope().kind {
|
||||
ScopeKind::Class(class_def) => &class_def.globals,
|
||||
ScopeKind::Function(function_def) => &function_def.globals,
|
||||
_ => return,
|
||||
};
|
||||
if let Some(stmt) = globals.get(name) {
|
||||
if expr.location < stmt.location {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UsedPriorGlobalDeclaration(name.to_string(), stmt.location.row()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/pylint/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NonlocalWithoutBinding: x
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
NonlocalWithoutBinding: y
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
end_location:
|
||||
row: 9
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
NonlocalWithoutBinding: y
|
||||
location:
|
||||
row: 19
|
||||
column: 8
|
||||
end_location:
|
||||
row: 19
|
||||
column: 18
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
source: src/pylint/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 7
|
||||
location:
|
||||
row: 5
|
||||
column: 10
|
||||
end_location:
|
||||
row: 5
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 17
|
||||
location:
|
||||
row: 15
|
||||
column: 10
|
||||
end_location:
|
||||
row: 15
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 25
|
||||
location:
|
||||
row: 23
|
||||
column: 10
|
||||
end_location:
|
||||
row: 23
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 35
|
||||
location:
|
||||
row: 33
|
||||
column: 10
|
||||
end_location:
|
||||
row: 33
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 43
|
||||
location:
|
||||
row: 41
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 53
|
||||
location:
|
||||
row: 51
|
||||
column: 4
|
||||
end_location:
|
||||
row: 51
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 61
|
||||
location:
|
||||
row: 59
|
||||
column: 8
|
||||
end_location:
|
||||
row: 59
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 71
|
||||
location:
|
||||
row: 69
|
||||
column: 8
|
||||
end_location:
|
||||
row: 69
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 79
|
||||
location:
|
||||
row: 77
|
||||
column: 8
|
||||
end_location:
|
||||
row: 77
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 89
|
||||
location:
|
||||
row: 87
|
||||
column: 8
|
||||
end_location:
|
||||
row: 87
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 97
|
||||
location:
|
||||
row: 95
|
||||
column: 8
|
||||
end_location:
|
||||
row: 95
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UsedPriorGlobalDeclaration:
|
||||
- x
|
||||
- 107
|
||||
location:
|
||||
row: 105
|
||||
column: 8
|
||||
end_location:
|
||||
row: 105
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/pylint/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
GlobalVariableNotAssigned: x
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
GlobalVariableNotAssigned: x
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
end_location:
|
||||
row: 9
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -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![],
|
||||
|
||||
Reference in New Issue
Block a user