Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
062c41b6f5 | ||
|
|
78889efa37 | ||
|
|
97fc281779 | ||
|
|
138b06c98a | ||
|
|
2415d73260 | ||
|
|
b060ae2f22 | ||
|
|
9aa91d3d3c | ||
|
|
dcedb801e5 | ||
|
|
d3e7fdabb5 | ||
|
|
dfa5a4f0f7 | ||
|
|
58a2d600da | ||
|
|
7ecbfe4f6a | ||
|
|
d8248104a7 | ||
|
|
6eb09122c0 | ||
|
|
f84c1f1fa1 | ||
|
|
3aa9528229 | ||
|
|
5a3f06bab1 | ||
|
|
db59d5b558 | ||
|
|
2fcbf3ab62 | ||
|
|
fa9b10be72 | ||
|
|
c495cef529 | ||
|
|
c0c8dff6ce | ||
|
|
80b00cc89f | ||
|
|
934db3d179 | ||
|
|
6a040a0405 | ||
|
|
2821ef0f69 | ||
|
|
343d931ddb | ||
|
|
3fc257f71b | ||
|
|
6dbb0a17e9 | ||
|
|
ae5ad6a4ac | ||
|
|
549af6c584 | ||
|
|
9a799eb4e6 | ||
|
|
f260b873b6 | ||
|
|
782a90b584 | ||
|
|
7df903dc4d | ||
|
|
8fc5e91ec7 | ||
|
|
9ca1a2c273 | ||
|
|
86265c1d7c | ||
|
|
a057c9a323 | ||
|
|
2e63bb6dcb | ||
|
|
1b5db80b32 | ||
|
|
3f20cea402 | ||
|
|
389fe1ff64 | ||
|
|
bad2d7ba85 | ||
|
|
416aa298ac | ||
|
|
a535b1adbf |
@@ -1,8 +1,8 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.40
|
||||
rev: v0.0.93
|
||||
hooks:
|
||||
- id: lint
|
||||
- id: ruff
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.10.1
|
||||
|
||||
179
Cargo.lock
generated
179
Cargo.lock
generated
@@ -37,6 +37,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.6.1"
|
||||
@@ -368,6 +374,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
@@ -416,6 +428,45 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap",
|
||||
"textwrap 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.0.15"
|
||||
@@ -425,7 +476,7 @@ dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"clap_lex 0.3.0",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
@@ -444,6 +495,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.0"
|
||||
@@ -466,6 +526,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codegen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@@ -539,6 +608,42 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap 3.2.23",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.6"
|
||||
@@ -1032,6 +1137,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1562,6 +1673,12 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
@@ -1770,6 +1887,34 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.3.0"
|
||||
@@ -2045,17 +2190,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.85"
|
||||
version = "0.0.93"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"cacache",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap 4.0.15",
|
||||
"clearscreen",
|
||||
"codegen",
|
||||
"colored",
|
||||
"common-path",
|
||||
"criterion",
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
@@ -2079,7 +2226,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"textwrap",
|
||||
"textwrap 0.15.1",
|
||||
"titlecase",
|
||||
"toml",
|
||||
"update-informer",
|
||||
@@ -2101,7 +2248,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2111,7 +2258,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2134,7 +2281,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2151,7 +2298,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2539,6 +2686,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
@@ -2579,6 +2732,16 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.85"
|
||||
version = "0.0.93"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -26,9 +26,9 @@ once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
@@ -50,6 +50,8 @@ getrandom = { version = "0.2.7", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
codegen = { version = "0.2.0" }
|
||||
criterion = { version = "0.4.0" }
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
|
||||
@@ -68,3 +70,7 @@ opt-level = 3
|
||||
|
||||
[profile.dev.package.similar]
|
||||
opt-level = 3
|
||||
|
||||
[[bench]]
|
||||
name = "source_code_locator"
|
||||
harness = false
|
||||
|
||||
194
README.md
194
README.md
@@ -38,13 +38,25 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
1. [Installation and Usage](#installation-and-usage)
|
||||
2. [Configuration](#configuration)
|
||||
3. [Supported Rules](#supported-rules)
|
||||
4. [Editor Integrations](#editor-integrations)
|
||||
5. [FAQ](#faq)
|
||||
6. [Development](#development)
|
||||
7. [Releases](#releases)
|
||||
8. [Benchmarks](#benchmarks)
|
||||
9. [License](#license)
|
||||
10. [Contributing](#contributing)
|
||||
1. [Pyflakes](#pyflakes)
|
||||
2. [pycodestyle (error)](#pycodestyle-error)
|
||||
3. [pycodestyle (warning)](#pycodestyle-warning)
|
||||
4. [pydocstyle](#pydocstyle)
|
||||
5. [pyupgrade](#pyupgrade)
|
||||
6. [pep8-naming](#pep8-naming)
|
||||
7. [flake8-comprehensions](#flake8-comprehensions)
|
||||
8. [flake8-bugbear](#flake8-bugbear)
|
||||
9. [flake8-builtins](#flake8-builtins)
|
||||
10. [flake8-print](#flake8-print)
|
||||
11. [flake8-quotes](#flake8-quotes)
|
||||
12. [Meta rules](#meta-rules)
|
||||
5. [Editor Integrations](#editor-integrations)
|
||||
6. [FAQ](#faq)
|
||||
7. [Development](#development)
|
||||
8. [Releases](#releases)
|
||||
9. [Benchmarks](#benchmarks)
|
||||
10. [License](#license)
|
||||
11. [Contributing](#contributing)
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
@@ -77,11 +89,14 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.85
|
||||
rev: v0.0.93
|
||||
hooks:
|
||||
- id: lint
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
<!-- TODO(charlie): Remove this message a few versions after v0.0.86. -->
|
||||
_Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as the hook ID._
|
||||
|
||||
## Configuration
|
||||
|
||||
Ruff is configurable both via `pyproject.toml` and the command line.
|
||||
@@ -91,13 +106,25 @@ For example, you could configure Ruff to only enforce a subset of rules with:
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
select = [
|
||||
"F401",
|
||||
"F403",
|
||||
select = ["E", "F"]
|
||||
ignore = ["E501"]
|
||||
per-file-ignores = [
|
||||
"__init__.py:F401",
|
||||
"path/to/file.py:F401"
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, on the command-line:
|
||||
Plugin configurations should be expressed as subsections, e.g.:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Alternatively, common configuration settings can be provided via the command-line:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/ --select F401 --select F403
|
||||
@@ -219,8 +246,7 @@ add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
## Supported Rules
|
||||
|
||||
By default, Ruff enables all `E`, `W`, and `F` error codes, which correspond to those built-in to
|
||||
Flake8.
|
||||
By default, Ruff enables all `E` and `F` error codes, which correspond to those built-in to Flake8.
|
||||
|
||||
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
|
||||
|
||||
@@ -246,7 +272,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` | |
|
||||
| F701 | BreakOutsideLoop | `break` outside loop | |
|
||||
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
|
||||
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
|
||||
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | |
|
||||
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
|
||||
@@ -315,7 +341,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| D400 | EndsInPeriod | First line should end with a period | |
|
||||
| D402 | NoSignature | First line should not be the function's signature | |
|
||||
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be `This` | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be 'This' | |
|
||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
|
||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
|
||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
|
||||
@@ -329,7 +355,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
|
||||
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
|
||||
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
|
||||
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | |
|
||||
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
|
||||
| D419 | NonEmpty | Docstring is empty | |
|
||||
|
||||
### pyupgrade
|
||||
@@ -354,12 +380,16 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| N803 | InvalidArgumentName | Argument name `...` should be lowercase | |
|
||||
| N804 | InvalidFirstArgumentNameForClassMethod | First argument of a class method should be named `cls` | |
|
||||
| N805 | InvalidFirstArgumentNameForMethod | First argument of a method should be named `self` | |
|
||||
| N806 | NonLowercaseVariableInFunction | Variable `...` in function should be lowercase | |
|
||||
| N807 | DunderFunctionName | Function name should not start and end with `__` | |
|
||||
| N811 | ConstantImportedAsNonConstant | Constant `...` imported as non-constant `...` | |
|
||||
| N812 | LowercaseImportedAsNonLowercase | Lowercase `...` imported as non-lowercase `...` | |
|
||||
| N813 | CamelcaseImportedAsLowercase | Camelcase `...` imported as lowercase `...` | |
|
||||
| N814 | CamelcaseImportedAsConstant | Camelcase `...` imported as constant `...` | |
|
||||
| N815 | MixedCaseVariableInClassScope | Variable `mixedCase` in class scope should not be mixedCase | |
|
||||
| N816 | MixedCaseVariableInGlobalScope | Variable `mixedCase` in global scope should not be mixedCase | |
|
||||
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
|
||||
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
|
||||
|
||||
### flake8-comprehensions
|
||||
|
||||
@@ -379,7 +409,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
|
||||
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
|
||||
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
|
||||
|
||||
### flake8-bugbear
|
||||
@@ -387,8 +417,10 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
@@ -408,6 +440,15 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| T201 | PrintFound | `print` found | 🛠 |
|
||||
| T203 | PPrintFound | `pprint` found | 🛠 |
|
||||
|
||||
### flake8-quotes
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | |
|
||||
| Q001 | BadQuotesMultilineString | Single quote multiline found but double quotes preferred | |
|
||||
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
|
||||
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
|
||||
|
||||
### Meta rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
@@ -474,13 +515,15 @@ Ruff re-implements some of the most popular Flake8 plugins and related code qual
|
||||
including:
|
||||
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
@@ -495,12 +538,14 @@ Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis F
|
||||
|
||||
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
|
||||
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
|
||||
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
|
||||
@@ -529,35 +574,55 @@ Yes! To enable a specific docstring convention, start by enabling all `pydocstyl
|
||||
then selectively disabling based on your [preferred convention](https://www.pydocstyle.org/en/latest/error_codes.html#default-conventions).
|
||||
|
||||
For example, if you're coming from `flake8-docstrings`, the following configuration is equivalent to
|
||||
`--docstring-convention numpy`:
|
||||
`--docstring-convention=numpy`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = [
|
||||
"D100",
|
||||
"D101",
|
||||
"D102",
|
||||
"D103",
|
||||
"D104",
|
||||
"D105",
|
||||
"D106",
|
||||
"D200",
|
||||
"D201",
|
||||
"D202",
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D107",
|
||||
"D203",
|
||||
"D212",
|
||||
"D213",
|
||||
"D402",
|
||||
"D413",
|
||||
"D415",
|
||||
"D416",
|
||||
"D417",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention=google`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D203",
|
||||
"D204",
|
||||
"D205",
|
||||
"D206",
|
||||
"D207",
|
||||
"D208",
|
||||
"D209",
|
||||
"D210",
|
||||
"D211",
|
||||
"D213",
|
||||
"D215",
|
||||
"D400",
|
||||
"D404",
|
||||
"D406",
|
||||
"D407",
|
||||
"D408",
|
||||
"D409",
|
||||
"D413",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention=pep8`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D203",
|
||||
"D212",
|
||||
"D213",
|
||||
"D214",
|
||||
"D215",
|
||||
"D300",
|
||||
"D400",
|
||||
"D402",
|
||||
"D403",
|
||||
"D404",
|
||||
"D405",
|
||||
"D406",
|
||||
@@ -566,51 +631,10 @@ extend-select = [
|
||||
"D409",
|
||||
"D410",
|
||||
"D411",
|
||||
"D412",
|
||||
"D413",
|
||||
"D418",
|
||||
"D419",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention google`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = [
|
||||
"D100",
|
||||
"D101",
|
||||
"D102",
|
||||
"D103",
|
||||
"D104",
|
||||
"D105",
|
||||
"D106",
|
||||
"D107",
|
||||
"D200",
|
||||
"D201",
|
||||
"D202",
|
||||
"D205",
|
||||
"D206",
|
||||
"D207",
|
||||
"D208",
|
||||
"D209",
|
||||
"D210",
|
||||
"D211",
|
||||
"D212",
|
||||
"D214",
|
||||
"D300",
|
||||
"D402",
|
||||
"D403",
|
||||
"D405",
|
||||
"D410",
|
||||
"D411",
|
||||
"D412",
|
||||
"D414",
|
||||
"D415",
|
||||
"D416",
|
||||
"D417",
|
||||
"D418",
|
||||
"D419",
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
16
benches/source_code_locator.rs
Normal file
16
benches/source_code_locator.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use ruff::fs;
|
||||
use ruff::source_code_locator::compute_offsets;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
c.bench_function("compute_offsets", |b| {
|
||||
b.iter(|| compute_offsets(black_box(&contents)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
105
examples/generate_check_code_prefix.rs
Normal file
105
examples/generate_check_code_prefix.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Generate the CheckCodePrefix enum.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::CheckCode;
|
||||
|
||||
fn main() {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
|
||||
for check_code in CheckCode::iter() {
|
||||
let as_ref = check_code.as_ref().to_string();
|
||||
for i in 1..=as_ref.len() {
|
||||
let prefix = as_ref[..i].to_string();
|
||||
let entry = prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_insert_with(|| Default::default());
|
||||
entry.insert(check_code.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Create the `CheckCodePrefix` definition.
|
||||
let mut gen = scope
|
||||
.new_enum("CheckCodePrefix")
|
||||
.vis("pub")
|
||||
.derive("EnumString")
|
||||
.derive("Debug")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("Clone")
|
||||
.derive("Serialize")
|
||||
.derive("Deserialize");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
gen = gen.push_variant(Variant::new(prefix.to_string()));
|
||||
}
|
||||
|
||||
// Create the `PrefixSpecificity` definition.
|
||||
scope
|
||||
.new_enum("PrefixSpecificity")
|
||||
.vis("pub")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.push_variant(Variant::new("Category"))
|
||||
.push_variant(Variant::new("Hundreds"))
|
||||
.push_variant(Variant::new("Tens"))
|
||||
.push_variant(Variant::new("Explicit"));
|
||||
|
||||
// Create the `match` statement, to map from definition to relevant codes.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("codes")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("Vec<CheckCode>"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
// Create the `match` statement, to map from definition to specificity.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("specificity")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("PrefixSpecificity"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
let specificity = match prefix.len() {
|
||||
4 => "Explicit",
|
||||
3 => "Tens",
|
||||
2 => "Hundreds",
|
||||
1 => "Category",
|
||||
_ => panic!("Invalid prefix: {}", prefix),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
|
||||
specificity
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Deserialize, Serialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
println!();
|
||||
println!("{}", scope.to_string());
|
||||
}
|
||||
187
resources/test/fixtures/B006_B008.py
vendored
Normal file
187
resources/test/fixtures/B006_B008.py
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
import collections
|
||||
import datetime as dt
|
||||
import logging
|
||||
import operator
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import types
|
||||
from operator import attrgetter, itemgetter, methodcaller
|
||||
from types import MappingProxyType
|
||||
|
||||
|
||||
# B006
|
||||
# Allow immutable literals/calls/comprehensions
|
||||
def this_is_okay(value=(1, 2, 3)):
|
||||
...
|
||||
|
||||
|
||||
async def and_this_also(value=tuple()):
|
||||
pass
|
||||
|
||||
|
||||
def frozenset_also_okay(value=frozenset()):
|
||||
pass
|
||||
|
||||
|
||||
def mappingproxytype_okay(
|
||||
value=MappingProxyType({}), value2=types.MappingProxyType({})
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def re_compile_ok(value=re.compile("foo")):
|
||||
pass
|
||||
|
||||
|
||||
def operators_ok(
|
||||
v=operator.attrgetter("foo"),
|
||||
v2=operator.itemgetter("foo"),
|
||||
v3=operator.methodcaller("foo"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def operators_ok_unqualified(
|
||||
v=attrgetter("foo"),
|
||||
v2=itemgetter("foo"),
|
||||
v3=methodcaller("foo"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def kwonlyargs_immutable(*, value=()):
|
||||
...
|
||||
|
||||
|
||||
# Flag mutable literals/comprehensions
|
||||
|
||||
|
||||
def this_is_wrong(value=[1, 2, 3]):
|
||||
...
|
||||
|
||||
|
||||
def this_is_also_wrong(value={}):
|
||||
...
|
||||
|
||||
|
||||
def and_this(value=set()):
|
||||
...
|
||||
|
||||
|
||||
def this_too(value=collections.OrderedDict()):
|
||||
...
|
||||
|
||||
|
||||
async def async_this_too(value=collections.defaultdict()):
|
||||
...
|
||||
|
||||
|
||||
def dont_forget_me(value=collections.deque()):
|
||||
...
|
||||
|
||||
|
||||
# N.B. we're also flagging the function call in the comprehension
|
||||
def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
pass
|
||||
|
||||
|
||||
def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
pass
|
||||
|
||||
|
||||
def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
pass
|
||||
|
||||
|
||||
def kwonlyargs_mutable(*, value=[]):
|
||||
...
|
||||
|
||||
|
||||
# Recommended approach for mutable defaults
|
||||
def do_this_instead(value=None):
|
||||
if value is None:
|
||||
value = set()
|
||||
|
||||
|
||||
# B008
|
||||
# Flag function calls as default args (including if they are part of a sub-expression)
|
||||
def in_fact_all_calls_are_wrong(value=time.time()):
|
||||
...
|
||||
|
||||
|
||||
def f(when=dt.datetime.now() + dt.timedelta(days=7)):
|
||||
pass
|
||||
|
||||
|
||||
def can_even_catch_lambdas(a=(lambda x: x)()):
|
||||
...
|
||||
|
||||
|
||||
# Recommended approach for function calls as default args
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def do_this_instead_of_calls_in_defaults(logger=LOGGER):
|
||||
# That makes it more obvious that this one value is reused.
|
||||
...
|
||||
|
||||
|
||||
# Handle inf/infinity/nan special case
|
||||
def float_inf_okay(value=float("inf")):
|
||||
pass
|
||||
|
||||
|
||||
def float_infinity_okay(value=float("infinity")):
|
||||
pass
|
||||
|
||||
|
||||
def float_plus_infinity_okay(value=float("+infinity")):
|
||||
pass
|
||||
|
||||
|
||||
def float_minus_inf_okay(value=float("-inf")):
|
||||
pass
|
||||
|
||||
|
||||
def float_nan_okay(value=float("nan")):
|
||||
pass
|
||||
|
||||
|
||||
def float_minus_NaN_okay(value=float("-NaN")):
|
||||
pass
|
||||
|
||||
|
||||
def float_infinity_literal(value=float("1e999")):
|
||||
pass
|
||||
|
||||
|
||||
# But don't allow standard floats
|
||||
def float_int_is_wrong(value=float(3)):
|
||||
pass
|
||||
|
||||
|
||||
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
|
||||
pass
|
||||
|
||||
|
||||
# B006 and B008
|
||||
# We should handle arbitrary nesting of these B008.
|
||||
def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
pass
|
||||
|
||||
|
||||
# Don't flag nested B006 since we can't guarantee that
|
||||
# it isn't made mutable by the outer operation.
|
||||
def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
pass
|
||||
|
||||
|
||||
# B008-ception.
|
||||
def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
pass
|
||||
|
||||
|
||||
# Ignore lambda contents since they are evaluated at call time.
|
||||
def foo(f=lambda x: print(x)):
|
||||
f(1)
|
||||
8
resources/test/fixtures/B013.py
vendored
Normal file
8
resources/test/fixtures/B013.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
except (ImportError, TypeError):
|
||||
pass
|
||||
3
resources/test/fixtures/C402.py
vendored
3
resources/test/fixtures/C402.py
vendored
@@ -1 +1,2 @@
|
||||
d = dict((x, x) for x in range(3))
|
||||
dict((x, x) for x in range(3))
|
||||
dict(((x, x) for x in range(3)), z=3)
|
||||
|
||||
3
resources/test/fixtures/C404.py
vendored
3
resources/test/fixtures/C404.py
vendored
@@ -1 +1,2 @@
|
||||
d = dict([(i, i) for i in range(3)])
|
||||
dict([(i, i) for i in range(3)])
|
||||
dict([(i, i) for i in range(3)], z=4)
|
||||
|
||||
15
resources/test/fixtures/N802.py
vendored
15
resources/test/fixtures/N802.py
vendored
@@ -1,3 +1,6 @@
|
||||
import unittest
|
||||
|
||||
|
||||
def Bad():
|
||||
pass
|
||||
|
||||
@@ -24,3 +27,15 @@ def _good():
|
||||
|
||||
def good_func():
|
||||
pass
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
pass
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
return super().tearDown()
|
||||
|
||||
def testTest(self):
|
||||
assert True
|
||||
|
||||
4
resources/test/fixtures/N806.py
vendored
Normal file
4
resources/test/fixtures/N806.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
def f():
|
||||
lower = 0
|
||||
Camel = 0
|
||||
CONSTANT = 0
|
||||
6
resources/test/fixtures/N815.py
vendored
Normal file
6
resources/test/fixtures/N815.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
5
resources/test/fixtures/N816.py
vendored
Normal file
5
resources/test/fixtures/N816.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
10
resources/test/fixtures/N818.py
vendored
Normal file
10
resources/test/fixtures/N818.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AnotherError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class C(Exception):
|
||||
pass
|
||||
2
resources/test/fixtures/__init__.py
vendored
2
resources/test/fixtures/__init__.py
vendored
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
print(__path__)
|
||||
|
||||
__all__ = ["a", "b", "c"]
|
||||
|
||||
38
resources/test/fixtures/flake8_quotes/docstring_doubles.py
vendored
Normal file
38
resources/test/fixtures/flake8_quotes/docstring_doubles.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Double quotes multiline module docstring
|
||||
"""
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
l = []
|
||||
|
||||
class Cls:
|
||||
"""
|
||||
Double quotes multiline class docstring
|
||||
"""
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
# The colon in the list indexing below is an edge case for the docstring scanner
|
||||
def f(self, bar="""
|
||||
definitely not a docstring""",
|
||||
val=l[Cls():3]):
|
||||
"""
|
||||
Double quotes multiline function docstring
|
||||
"""
|
||||
|
||||
some_expression = 'hello world'
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
if l:
|
||||
"""
|
||||
Looks like a docstring, but in reality it isn't - only modules, classes and functions
|
||||
"""
|
||||
pass
|
||||
9
resources/test/fixtures/flake8_quotes/docstring_doubles_class.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/docstring_doubles_class.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
""" Double quotes single line class docstring """
|
||||
""" Not a docstring """
|
||||
|
||||
def foo(self, bar="""not a docstring"""):
|
||||
""" Double quotes single line method docstring"""
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): """ inline docstring """; pass
|
||||
22
resources/test/fixtures/flake8_quotes/docstring_doubles_function.py
vendored
Normal file
22
resources/test/fixtures/flake8_quotes/docstring_doubles_function.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
def foo():
|
||||
"""function without params, single line docstring"""
|
||||
""" not a docstring"""
|
||||
return
|
||||
|
||||
|
||||
def foo2():
|
||||
"""
|
||||
function without params, multiline docstring
|
||||
"""
|
||||
""" not a docstring"""
|
||||
return
|
||||
|
||||
|
||||
def fun_with_params_no_docstring(a, b="""
|
||||
not a
|
||||
""" """docstring"""):
|
||||
pass
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
""" not a docstring """):
|
||||
pass
|
||||
11
resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py
vendored
Normal file
11
resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Double quotes multiline module docstring
|
||||
"""
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
def foo():
|
||||
pass
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
6
resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py
vendored
Normal file
6
resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
""" Double quotes singleline module docstring """
|
||||
""" this is not a docstring """
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
40
resources/test/fixtures/flake8_quotes/docstring_singles.py
vendored
Normal file
40
resources/test/fixtures/flake8_quotes/docstring_singles.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
'''
|
||||
Single quotes multiline module docstring
|
||||
'''
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
l = []
|
||||
|
||||
class Cls(MakeKlass('''
|
||||
class params \t not a docstring
|
||||
''')):
|
||||
'''
|
||||
Single quotes multiline class docstring
|
||||
'''
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
# The colon in the list indexing below is an edge case for the docstring scanner
|
||||
def f(self, bar='''
|
||||
definitely not a docstring''',
|
||||
val=l[Cls():3]):
|
||||
'''
|
||||
Single quotes multiline function docstring
|
||||
'''
|
||||
|
||||
some_expression = 'hello world'
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
if l:
|
||||
'''
|
||||
Looks like a docstring, but in reality it isn't - only modules, classes and functions
|
||||
'''
|
||||
pass
|
||||
9
resources/test/fixtures/flake8_quotes/docstring_singles_class.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/docstring_singles_class.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
''' Double quotes single line class docstring '''
|
||||
''' Not a docstring '''
|
||||
|
||||
def foo(self, bar='''not a docstring'''):
|
||||
''' Double quotes single line method docstring'''
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): ''' inline docstring '''; pass
|
||||
23
resources/test/fixtures/flake8_quotes/docstring_singles_function.py
vendored
Normal file
23
resources/test/fixtures/flake8_quotes/docstring_singles_function.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
def foo():
|
||||
'''function without params, single line docstring'''
|
||||
''' not a docstring'''
|
||||
return
|
||||
|
||||
|
||||
def foo2():
|
||||
'''
|
||||
function without params, multiline docstring
|
||||
'''
|
||||
''' not a docstring'''
|
||||
return
|
||||
|
||||
|
||||
def fun_with_params_no_docstring(a, b='''
|
||||
not a
|
||||
''' '''docstring'''):
|
||||
pass
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
''' not a docstring '''):
|
||||
pass
|
||||
|
||||
11
resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py
vendored
Normal file
11
resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
'''
|
||||
Double quotes multiline module docstring
|
||||
'''
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
def foo():
|
||||
pass
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
6
resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py
vendored
Normal file
6
resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
''' Double quotes singleline module docstring '''
|
||||
''' this is not a docstring '''
|
||||
|
||||
def foo():
|
||||
pass
|
||||
''' this is not a docstring '''
|
||||
2
resources/test/fixtures/flake8_quotes/doubles.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/doubles.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
this_should_be_linted = "double quote string"
|
||||
this_should_be_linted = u"double quote string"
|
||||
5
resources/test/fixtures/flake8_quotes/doubles_escaped.py
vendored
Normal file
5
resources/test/fixtures/flake8_quotes/doubles_escaped.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
this_should_raise_Q003 = 'This is a \'string\''
|
||||
this_is_fine = '"This" is a \'string\''
|
||||
this_is_fine = "This is a 'string'"
|
||||
this_is_fine = "\"This\" is a 'string'"
|
||||
this_is_fine = r'This is a \'string\''
|
||||
9
resources/test/fixtures/flake8_quotes/doubles_multiline_string.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/doubles_multiline_string.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
s = """ This "should"
|
||||
be
|
||||
"linted" """
|
||||
|
||||
s = ''' This "should"
|
||||
"not" be
|
||||
"linted" '''
|
||||
|
||||
s = """'This should not be linted due to having would-be quadruple end quote'"""
|
||||
1
resources/test/fixtures/flake8_quotes/doubles_noqa.py
vendored
Normal file
1
resources/test/fixtures/flake8_quotes/doubles_noqa.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this_should_not_be_linted = "double quote string" # noqa
|
||||
2
resources/test/fixtures/flake8_quotes/doubles_wrapped.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/doubles_wrapped.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = 'double "quotes" wrapped in singles are ignored'
|
||||
s = "single 'quotes' wrapped in doubles are ignored"
|
||||
2
resources/test/fixtures/flake8_quotes/singles.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/singles.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
this_should_be_linted = 'single quote string'
|
||||
this_should_be_linted = u'double quote string'
|
||||
5
resources/test/fixtures/flake8_quotes/singles_escaped.py
vendored
Normal file
5
resources/test/fixtures/flake8_quotes/singles_escaped.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
this_should_raise_Q003 = "This is a \"string\""
|
||||
this_is_fine = "'This' is a \"string\""
|
||||
this_is_fine = 'This is a "string"'
|
||||
this_is_fine = '\'This\' is a "string"'
|
||||
this_is_fine = r"This is a \"string\""
|
||||
9
resources/test/fixtures/flake8_quotes/singles_multiline_string.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/singles_multiline_string.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
s = ''' This 'should'
|
||||
be
|
||||
'linted' '''
|
||||
|
||||
s = """ This 'should'
|
||||
'not' be
|
||||
'linted' """
|
||||
|
||||
s = '''"This should not be linted due to having would-be quadruple end quote"'''
|
||||
1
resources/test/fixtures/flake8_quotes/singles_noqa.py
vendored
Normal file
1
resources/test/fixtures/flake8_quotes/singles_noqa.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this_should_not_be_linted = 'single quote string' # noqa
|
||||
2
resources/test/fixtures/flake8_quotes/singles_wrapped.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/singles_wrapped.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = "single 'quotes' wrapped in doubles are ignored"
|
||||
s = 'double "quotes" wrapped in singles are ignored'
|
||||
31
resources/test/fixtures/pyproject.toml
vendored
31
resources/test/fixtures/pyproject.toml
vendored
@@ -5,3 +5,34 @@ extend-exclude = [
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = [
|
||||
"__init__.py:F401",
|
||||
]
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
multiline-quotes = "double"
|
||||
docstring-quotes = "double"
|
||||
avoid-escape = true
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
]
|
||||
|
||||
@@ -137,7 +137,7 @@ pub fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
if relative.row() == 1 {
|
||||
Location::new(
|
||||
relative.row() + base.row() - 1,
|
||||
relative.column() + base.column() - 1,
|
||||
relative.column() + base.column(),
|
||||
)
|
||||
} else {
|
||||
Location::new(relative.row() + base.row() - 1, relative.column())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::{BindingKind, Range, Scope};
|
||||
use crate::ast::types::{BindingKind, Scope};
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
@@ -117,82 +117,3 @@ pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||
pub struct SourceCodeLocator<'a> {
|
||||
content: &'a str,
|
||||
offsets: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl<'a> SourceCodeLocator<'a> {
|
||||
pub fn new(content: &'a str) -> Self {
|
||||
SourceCodeLocator {
|
||||
content,
|
||||
offsets: Self::compute_offsets(content),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_offsets(content: &str) -> Vec<Vec<usize>> {
|
||||
let mut offsets = vec![];
|
||||
let mut offset = 0;
|
||||
for line in content.lines() {
|
||||
let mut newline = 0;
|
||||
let mut line_offsets: Vec<usize> = vec![];
|
||||
for (i, char) in line.char_indices() {
|
||||
line_offsets.push(offset + i);
|
||||
newline = i + char.len_utf8();
|
||||
}
|
||||
line_offsets.push(offset + newline);
|
||||
offsets.push(line_offsets);
|
||||
offset += newline + 1;
|
||||
}
|
||||
offsets.push(vec![offset]);
|
||||
offsets
|
||||
}
|
||||
|
||||
pub fn slice_source_code_at(&self, location: &Location) -> &'a str {
|
||||
let offset = self.offsets[location.row() - 1][location.column() - 1];
|
||||
&self.content[offset..]
|
||||
}
|
||||
|
||||
pub fn slice_source_code_range(&self, range: &Range) -> &'a str {
|
||||
let start = self.offsets[range.location.row() - 1][range.location.column() - 1];
|
||||
let end = self.offsets[range.end_location.row() - 1][range.end_location.column() - 1];
|
||||
&self.content[start..end]
|
||||
}
|
||||
|
||||
pub fn partition_source_code_at(
|
||||
&self,
|
||||
outer: &Range,
|
||||
inner: &Range,
|
||||
) -> (&'a str, &'a str, &'a str) {
|
||||
let outer_start = self.offsets[outer.location.row() - 1][outer.location.column() - 1];
|
||||
let outer_end = self.offsets[outer.end_location.row() - 1][outer.end_location.column() - 1];
|
||||
let inner_start = self.offsets[inner.location.row() - 1][inner.location.column() - 1];
|
||||
let inner_end = self.offsets[inner.end_location.row() - 1][inner.end_location.column() - 1];
|
||||
(
|
||||
&self.content[outer_start..inner_start],
|
||||
&self.content[inner_start..inner_end],
|
||||
&self.content[inner_end..outer_end],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn source_code_locator_init() {
|
||||
let content = "# \u{4e9c}\nclass Foo:\n \"\"\".\"\"\"";
|
||||
let locator = SourceCodeLocator::new(content);
|
||||
assert_eq!(locator.offsets.len(), 4);
|
||||
assert_eq!(locator.offsets[0], [0, 1, 2, 5]);
|
||||
assert_eq!(locator.offsets[1], [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
||||
assert_eq!(
|
||||
locator.offsets[2],
|
||||
[17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
|
||||
);
|
||||
assert_eq!(locator.offsets[3], [29]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,19 +69,18 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
|
||||
|
||||
if fix.patch.location.row() > last_pos.row() {
|
||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column()..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..fix.patch.location.row() - 1] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column() - 1]);
|
||||
output.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column()]);
|
||||
output.push_str(&fix.patch.content);
|
||||
} else {
|
||||
output.push_str(
|
||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.patch.location.column() - 1],
|
||||
&lines[last_pos.row() - 1][last_pos.column()..fix.patch.location.column()],
|
||||
);
|
||||
output.push_str(&fix.patch.content);
|
||||
}
|
||||
@@ -95,7 +94,7 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
|
||||
&& (last_pos.row() - 1) < lines.len()
|
||||
&& (last_pos.row() > 0 || last_pos.column() > 0)
|
||||
{
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column()..]);
|
||||
output.push('\n');
|
||||
}
|
||||
if last_pos.row() < lines.len() {
|
||||
@@ -133,8 +132,8 @@ mod tests {
|
||||
let mut fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "Bar".to_string(),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 15),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 14),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
@@ -159,8 +158,8 @@ mod tests {
|
||||
let mut fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
@@ -186,16 +185,16 @@ mod tests {
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 17),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 16),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 17),
|
||||
end_location: Location::new(1, 24),
|
||||
location: Location::new(1, 16),
|
||||
end_location: Location::new(1, 23),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
@@ -222,16 +221,16 @@ mod tests {
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "ignored".to_string(),
|
||||
location: Location::new(1, 10),
|
||||
end_location: Location::new(1, 12),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 11),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
|
||||
@@ -82,8 +82,8 @@ pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Res
|
||||
// Otherwise, nuke the entire line.
|
||||
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
|
||||
Ok(Fix::deletion(
|
||||
Location::new(stmt.location.row(), 1),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 1),
|
||||
Location::new(stmt.location.row(), 0),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
141
src/check_ast.rs
141
src/check_ast.rs
@@ -5,7 +5,6 @@ use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use log::error;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -13,7 +12,7 @@ use rustpython_parser::ast::{
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingContext, BindingKind, CheckLocator, FunctionScope, ImportKind, Range, Scope,
|
||||
@@ -26,7 +25,9 @@ use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::settings::{PythonVersion, Settings};
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, pep8_naming,
|
||||
@@ -38,13 +39,11 @@ pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
path: &'a Path,
|
||||
content: &'a str,
|
||||
autofix: &'a fixer::Mode,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Efficient source-code slicing.
|
||||
locator: OnceCell<SourceCodeLocator<'a>>,
|
||||
// Docstring tracking.
|
||||
docstrings: Vec<(Definition<'a>, Visibility)>,
|
||||
// Edit tracking.
|
||||
@@ -78,14 +77,13 @@ impl<'a> Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
autofix: &'a fixer::Mode,
|
||||
path: &'a Path,
|
||||
content: &'a str,
|
||||
locator: &'a SourceCodeLocator,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
autofix,
|
||||
path,
|
||||
content,
|
||||
locator: OnceCell::new(),
|
||||
locator,
|
||||
checks: Default::default(),
|
||||
docstrings: Default::default(),
|
||||
deletions: Default::default(),
|
||||
@@ -113,12 +111,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get access to a lazily-initialized `SourceCodeLocator` for the file contents.
|
||||
pub fn get_locator(&self) -> &SourceCodeLocator {
|
||||
self.locator
|
||||
.get_or_init(|| SourceCodeLocator::new(self.content))
|
||||
}
|
||||
|
||||
/// Return `true` if a patch should be generated under the given autofix `Mode`.
|
||||
pub fn patch(&self) -> bool {
|
||||
self.autofix.patch()
|
||||
@@ -235,7 +227,11 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N802) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_function_name(stmt, name) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_function_name(
|
||||
stmt,
|
||||
name,
|
||||
&self.settings.pep8_naming,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -246,6 +242,7 @@ where
|
||||
self.current_scope(),
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -257,6 +254,7 @@ where
|
||||
self.current_scope(),
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
@@ -362,6 +360,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N818) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::error_suffix_on_exception_name(stmt, bases, name)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -381,7 +387,7 @@ where
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 1 {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -487,7 +493,7 @@ where
|
||||
level,
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 1 {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -705,6 +711,9 @@ where
|
||||
{
|
||||
flake8_bugbear::plugins::duplicate_exceptions(self, stmt, handlers);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B013) {
|
||||
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -915,7 +924,6 @@ where
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::U005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
@@ -935,25 +943,25 @@ where
|
||||
|
||||
// flake8-comprehensions
|
||||
if self.settings.enabled.contains(&CheckCode::C400) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_list(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
|
||||
expr, func, args, keywords,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_set(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
|
||||
expr, func, args, keywords,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_dict(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
|
||||
expr, func, args, keywords,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -961,7 +969,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
|
||||
expr, func, args,
|
||||
expr, func, args, keywords,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -971,7 +979,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
|
||||
expr, func, args,
|
||||
expr, func, args, keywords,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -979,17 +987,17 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_set(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
|
||||
expr, func, args, keywords,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_dict(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr, func, args, keywords,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -1495,6 +1503,9 @@ where
|
||||
self.checks
|
||||
.extend(pyflakes::checks::duplicate_arguments(arguments));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B006) {
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
|
||||
}
|
||||
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them upstream.
|
||||
for arg in &arguments.posonlyargs {
|
||||
@@ -1798,6 +1809,30 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N806) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::non_lowercase_variable_in_function(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N815) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_class_scope(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N816) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_global_scope(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
@@ -2104,7 +2139,7 @@ impl<'a> Checker<'a> {
|
||||
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
|
||||
};
|
||||
|
||||
match removal_fn(self.get_locator(), &full_names, child, parent, &deleted) {
|
||||
match removal_fn(self.locator, &full_names, child, parent, &deleted) {
|
||||
Ok(fix) => Some(fix),
|
||||
Err(e) => {
|
||||
error!("Failed to fix unused imports: {}", e);
|
||||
@@ -2115,15 +2150,27 @@ impl<'a> Checker<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_names.into_iter().map(String::from).collect()),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
if let Some(fix) = fix {
|
||||
check.amend(fix);
|
||||
if self.path.ends_with("__init__.py") {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
true,
|
||||
),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
));
|
||||
} else {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
false,
|
||||
),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
if let Some(fix) = fix {
|
||||
check.amend(fix);
|
||||
}
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2252,12 +2299,12 @@ impl<'a> Checker<'a> {
|
||||
|
||||
pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
contents: &str,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, autofix, path, contents);
|
||||
let mut checker = Checker::new(settings, autofix, path, locator);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ pub fn check_lines(
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Range {
|
||||
location: Location::new(lineno + 1, 1),
|
||||
end_location: Location::new(lineno + 1, line_length + 1),
|
||||
location: Location::new(lineno + 1, 0),
|
||||
end_location: Location::new(lineno + 1, line_length),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -164,14 +164,14 @@ pub fn check_lines(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(None),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix.patch() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
}
|
||||
line_checks.push(check);
|
||||
@@ -192,21 +192,21 @@ pub fn check_lines(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes)),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix.patch() {
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
} else {
|
||||
check.amend(Fix::replacement(
|
||||
format!(" # noqa: {}", valid_codes.join(", ")),
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ pub fn check_lines(
|
||||
mod tests {
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings;
|
||||
use crate::settings::Settings;
|
||||
|
||||
use super::check_lines;
|
||||
|
||||
@@ -243,9 +243,9 @@ mod tests {
|
||||
&mut checks,
|
||||
line,
|
||||
&noqa_line_for,
|
||||
&settings::Settings {
|
||||
&Settings {
|
||||
line_length,
|
||||
..settings::Settings::for_rule(CheckCode::E501)
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
|
||||
@@ -1,123 +1,50 @@
|
||||
//! Lint rules based on token traversal.
|
||||
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::Settings;
|
||||
|
||||
// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
|
||||
const VALID_ESCAPE_SEQUENCES: &[char; 23] = &[
|
||||
'\n', '\\', '\'', '"', 'a', 'b', 'f', 'n', 'r', 't', 'v', '0', '1', '2', '3', '4', '5', '6',
|
||||
'7', 'x', // Escape sequences only recognized in string literals
|
||||
'N', 'u', 'U',
|
||||
];
|
||||
|
||||
/// Return the quotation markers used for a String token.
|
||||
fn extract_quote(text: &str) -> &str {
|
||||
if text.len() >= 3 {
|
||||
let triple = &text[text.len() - 3..];
|
||||
if triple == "'''" || triple == "\"\"\"" {
|
||||
return triple;
|
||||
}
|
||||
}
|
||||
|
||||
if !text.is_empty() {
|
||||
let single = &text[text.len() - 1..];
|
||||
if single == "'" || single == "\"" {
|
||||
return single;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Unable to find quotation mark for String token.")
|
||||
}
|
||||
|
||||
/// W605
|
||||
fn invalid_escape_sequence(
|
||||
locator: &SourceCodeLocator,
|
||||
start: &Location,
|
||||
end: &Location,
|
||||
) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
|
||||
let text = locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
});
|
||||
|
||||
// Determine whether the string is single- or triple-quoted.
|
||||
let quote = extract_quote(text);
|
||||
let quote_pos = text.find(quote).unwrap();
|
||||
let prefix = text[..quote_pos].to_lowercase();
|
||||
let body = &text[(quote_pos + quote.len())..(text.len() - quote.len())];
|
||||
|
||||
if !prefix.contains('r') {
|
||||
let mut col_offset = 0;
|
||||
let mut row_offset = 0;
|
||||
let mut in_escape = false;
|
||||
let mut chars = body.chars();
|
||||
let mut current = chars.next();
|
||||
let mut next = chars.next();
|
||||
while let (Some(current_char), Some(next_char)) = (current, next) {
|
||||
// If we see an escaped backslash, avoid treating the character _after_ the
|
||||
// escaped backslash as itself an escaped character.
|
||||
if in_escape {
|
||||
in_escape = false;
|
||||
} else {
|
||||
in_escape = current_char == '\\' && next_char == '\\';
|
||||
if current_char == '\\' && !VALID_ESCAPE_SEQUENCES.contains(&next_char) {
|
||||
// Compute the location of the escape sequence by offsetting the location of the
|
||||
// string token by the characters we've seen thus far.
|
||||
let location = if row_offset == 0 {
|
||||
Location::new(
|
||||
start.row() + row_offset,
|
||||
start.column() + prefix.len() + quote.len() + col_offset,
|
||||
)
|
||||
} else {
|
||||
Location::new(start.row() + row_offset, col_offset + 1)
|
||||
};
|
||||
let end_location = Location::new(location.row(), location.column() + 1);
|
||||
checks.push(Check::new(
|
||||
CheckKind::InvalidEscapeSequence(next_char),
|
||||
Range {
|
||||
location,
|
||||
end_location,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Track the offset from the start position as we iterate over the body.
|
||||
if current_char == '\n' {
|
||||
col_offset = 0;
|
||||
row_offset += 1;
|
||||
} else {
|
||||
col_offset += 1;
|
||||
}
|
||||
|
||||
current = next;
|
||||
next = chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::flake8_quotes::docstring_detection::StateMachine;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{flake8_quotes, pycodestyle, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
checks: &mut Vec<Check>,
|
||||
contents: &str,
|
||||
locator: &SourceCodeLocator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
) {
|
||||
// TODO(charlie): Use a shared SourceCodeLocator between this site and the AST traversal.
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
let enforce_quotes = settings.enabled.contains(&CheckCode::Q000)
|
||||
| settings.enabled.contains(&CheckCode::Q001)
|
||||
| settings.enabled.contains(&CheckCode::Q002)
|
||||
| settings.enabled.contains(&CheckCode::Q003);
|
||||
|
||||
let mut state_machine = StateMachine::new();
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
checks.extend(invalid_escape_sequence(&locator, start, end));
|
||||
checks.extend(pycodestyle::checks::invalid_escape_sequence(
|
||||
locator, start, end,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-quotes
|
||||
if enforce_quotes {
|
||||
let is_docstring = state_machine.consume(tok);
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
if let Some(check) = flake8_quotes::checks::quotes(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
is_docstring,
|
||||
&settings.flake8_quotes,
|
||||
) {
|
||||
if settings.enabled.contains(check.kind.code()) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
127
src/checks.rs
127
src/checks.rs
@@ -7,6 +7,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::pyupgrade::types::Primitive;
|
||||
|
||||
#[derive(
|
||||
@@ -77,8 +78,10 @@ pub enum CheckCode {
|
||||
A003,
|
||||
// flake8-bugbear
|
||||
B002,
|
||||
B006,
|
||||
B007,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
B017,
|
||||
B025,
|
||||
@@ -102,6 +105,11 @@ pub enum CheckCode {
|
||||
// flake8-print
|
||||
T201,
|
||||
T203,
|
||||
// flake8-quotes
|
||||
Q000,
|
||||
Q001,
|
||||
Q002,
|
||||
Q003,
|
||||
// pyupgrade
|
||||
U001,
|
||||
U002,
|
||||
@@ -162,12 +170,16 @@ pub enum CheckCode {
|
||||
N803,
|
||||
N804,
|
||||
N805,
|
||||
N806,
|
||||
N807,
|
||||
N811,
|
||||
N812,
|
||||
N813,
|
||||
N814,
|
||||
N815,
|
||||
N816,
|
||||
N817,
|
||||
N818,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
@@ -184,6 +196,7 @@ pub enum CheckCategory {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Meta,
|
||||
}
|
||||
|
||||
@@ -197,6 +210,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
CheckCategory::Pyupgrade => "pyupgrade",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
@@ -265,7 +279,7 @@ pub enum CheckKind {
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(Vec<String>),
|
||||
UnusedImport(Vec<String>, bool),
|
||||
UnusedVariable(String),
|
||||
YieldOutsideFunction,
|
||||
// flake8-builtins
|
||||
@@ -274,8 +288,10 @@ pub enum CheckKind {
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-bugbear
|
||||
UnaryPrefixIncrement,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
DoNotAssertFalse,
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
NoAssertRaisesException,
|
||||
DuplicateTryBlockException(String),
|
||||
@@ -299,6 +315,11 @@ pub enum CheckKind {
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
// flake8-quotes
|
||||
BadQuotesInlineString(Quote),
|
||||
BadQuotesMultilineString(Quote),
|
||||
BadQuotesDocstring(Quote),
|
||||
AvoidQuoteEscape,
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UnnecessaryAbspath,
|
||||
@@ -359,12 +380,16 @@ pub enum CheckKind {
|
||||
InvalidArgumentName(String),
|
||||
InvalidFirstArgumentNameForClassMethod,
|
||||
InvalidFirstArgumentNameForMethod,
|
||||
NonLowercaseVariableInFunction(String),
|
||||
DunderFunctionName,
|
||||
ConstantImportedAsNonConstant(String, String),
|
||||
LowercaseImportedAsNonLowercase(String, String),
|
||||
CamelcaseImportedAsLowercase(String, String),
|
||||
CamelcaseImportedAsConstant(String, String),
|
||||
MixedCaseVariableInClassScope(String),
|
||||
MixedCaseVariableInGlobalScope(String),
|
||||
CamelcaseImportedAsAcronym(String, String),
|
||||
ErrorSuffixOnExceptionName(String),
|
||||
// Meta
|
||||
UnusedNOQA(Option<Vec<String>>),
|
||||
}
|
||||
@@ -374,7 +399,11 @@ impl CheckCode {
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
|
||||
CheckCode::W605 => &LintSource::Tokens,
|
||||
CheckCode::W605
|
||||
| CheckCode::Q000
|
||||
| CheckCode::Q001
|
||||
| CheckCode::Q002
|
||||
| CheckCode::Q003 => &LintSource::Tokens,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
@@ -402,7 +431,7 @@ impl CheckCode {
|
||||
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
|
||||
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
|
||||
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()], false),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
CheckCode::F404 => CheckKind::LateFutureImport,
|
||||
@@ -438,8 +467,12 @@ impl CheckCode {
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-bugbear
|
||||
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
}
|
||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
@@ -476,6 +509,11 @@ impl CheckCode {
|
||||
// flake8-print
|
||||
CheckCode::T201 => CheckKind::PrintFound,
|
||||
CheckCode::T203 => CheckKind::PPrintFound,
|
||||
// flake8-quotes
|
||||
CheckCode::Q000 => CheckKind::BadQuotesInlineString(Quote::Double),
|
||||
CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double),
|
||||
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
|
||||
CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
|
||||
// pyupgrade
|
||||
CheckCode::U001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
|
||||
@@ -545,6 +583,7 @@ impl CheckCode {
|
||||
CheckCode::N803 => CheckKind::InvalidArgumentName("...".to_string()),
|
||||
CheckCode::N804 => CheckKind::InvalidFirstArgumentNameForClassMethod,
|
||||
CheckCode::N805 => CheckKind::InvalidFirstArgumentNameForMethod,
|
||||
CheckCode::N806 => CheckKind::NonLowercaseVariableInFunction("...".to_string()),
|
||||
CheckCode::N807 => CheckKind::DunderFunctionName,
|
||||
CheckCode::N811 => {
|
||||
CheckKind::ConstantImportedAsNonConstant("...".to_string(), "...".to_string())
|
||||
@@ -558,9 +597,12 @@ impl CheckCode {
|
||||
CheckCode::N814 => {
|
||||
CheckKind::CamelcaseImportedAsConstant("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N815 => CheckKind::MixedCaseVariableInClassScope("mixedCase".to_string()),
|
||||
CheckCode::N816 => CheckKind::MixedCaseVariableInGlobalScope("mixedCase".to_string()),
|
||||
CheckCode::N817 => {
|
||||
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
@@ -616,8 +658,10 @@ impl CheckCode {
|
||||
CheckCode::A002 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B006 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
@@ -639,6 +683,10 @@ impl CheckCode {
|
||||
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
CheckCode::Q000 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q001 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q002 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q003 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::U001 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U002 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U003 => CheckCategory::Pyupgrade,
|
||||
@@ -696,12 +744,16 @@ impl CheckCode {
|
||||
CheckCode::N803 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N804 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N805 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N806 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N807 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N811 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N812 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N813 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N814 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N815 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N816 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::M001 => CheckCategory::Meta,
|
||||
}
|
||||
}
|
||||
@@ -751,7 +803,7 @@ impl CheckKind {
|
||||
CheckKind::UndefinedExport(_) => &CheckCode::F822,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedImport(_, _) => &CheckCode::F401,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
// pycodestyle warnings
|
||||
@@ -763,8 +815,10 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
@@ -788,6 +842,11 @@ impl CheckKind {
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
CheckKind::PPrintFound => &CheckCode::T203,
|
||||
// flake8-quotes
|
||||
CheckKind::BadQuotesInlineString(_) => &CheckCode::Q000,
|
||||
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
|
||||
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
|
||||
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
|
||||
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
|
||||
@@ -848,12 +907,16 @@ impl CheckKind {
|
||||
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
|
||||
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
|
||||
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
|
||||
CheckKind::NonLowercaseVariableInFunction(..) => &CheckCode::N806,
|
||||
CheckKind::DunderFunctionName => &CheckCode::N807,
|
||||
CheckKind::ConstantImportedAsNonConstant(..) => &CheckCode::N811,
|
||||
CheckKind::LowercaseImportedAsNonLowercase(..) => &CheckCode::N812,
|
||||
CheckKind::CamelcaseImportedAsLowercase(..) => &CheckCode::N813,
|
||||
CheckKind::CamelcaseImportedAsConstant(..) => &CheckCode::N814,
|
||||
CheckKind::MixedCaseVariableInClassScope(..) => &CheckCode::N815,
|
||||
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
|
||||
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
|
||||
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
}
|
||||
@@ -980,15 +1043,19 @@ impl CheckKind {
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UnusedImport(names) => {
|
||||
CheckKind::UnusedImport(names, in_init_py) => {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("{names} imported but unused")
|
||||
if *in_init_py {
|
||||
format!("{names} imported but unused and missing from `__all__`")
|
||||
} else {
|
||||
format!("{names} imported but unused")
|
||||
}
|
||||
}
|
||||
CheckKind::UnusedVariable(name) => {
|
||||
format!("Local variable `{name}` is assigned to but never used")
|
||||
}
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"`yield` or `yield from` statement outside of a function/method".to_string()
|
||||
"`yield` or `yield from` statement outside of a function".to_string()
|
||||
}
|
||||
// pycodestyle warnings
|
||||
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
|
||||
@@ -1005,11 +1072,15 @@ impl CheckKind {
|
||||
}
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => "Python does not support the unary prefix increment. Writing `++n` is equivalent to `+(+(n))`, which equals `n`. You meant `n += 1`.".to_string(),
|
||||
CheckKind::MutableArgumentDefault => "Do not use mutable data structures for argument defaults.".to_string(),
|
||||
CheckKind::UnusedLoopControlVariable(name) => format!("Loop control variable `{name}` not used within the loop body. If this is intended, start the name with an underscore."),
|
||||
CheckKind::DoNotAssertFalse => {
|
||||
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::RedundantTupleInExceptionHandler(name) => {
|
||||
format!("A length-one tuple literal is redundant. Write `except {name}:` instead of `except ({name},):`.")
|
||||
}
|
||||
CheckKind::DuplicateHandlerException(names) => {
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
@@ -1085,7 +1156,7 @@ impl CheckKind {
|
||||
format!("Unnecessary subscript reversal of iterable within `{func}()`")
|
||||
}
|
||||
CheckKind::UnnecessaryComprehension(obj_type) => {
|
||||
format!(" Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)")
|
||||
format!("Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)")
|
||||
}
|
||||
CheckKind::UnnecessaryMap(obj_type) => {
|
||||
if obj_type == "generator" {
|
||||
@@ -1097,6 +1168,26 @@ impl CheckKind {
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => "`print` found".to_string(),
|
||||
CheckKind::PPrintFound => "`pprint` found".to_string(),
|
||||
// flake8-quotes
|
||||
CheckKind::BadQuotesInlineString(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quotes found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quotes found but double quotes preferred".to_string(),
|
||||
}
|
||||
},
|
||||
CheckKind::BadQuotesMultilineString(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quote multiline found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quote multiline found but double quotes preferred".to_string(),
|
||||
}
|
||||
},
|
||||
CheckKind::BadQuotesDocstring(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quote docstring found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quote docstring found but double quotes preferred".to_string(),
|
||||
}
|
||||
},
|
||||
CheckKind::AvoidQuoteEscape => "Change outer quotes to avoid escaping inner quotes".to_string(),
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -1106,7 +1197,7 @@ impl CheckKind {
|
||||
}
|
||||
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
|
||||
CheckKind::DeprecatedUnittestAlias(alias, target) => {
|
||||
format!("`{}` is deprecated, use `{}` instead", alias, target)
|
||||
format!("`{alias}` is deprecated, use `{target}` instead")
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
@@ -1176,10 +1267,10 @@ impl CheckKind {
|
||||
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
|
||||
CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
|
||||
CheckKind::NoThisPrefix => {
|
||||
"First word of the docstring should not be `This`".to_string()
|
||||
"First word of the docstring should not be 'This'".to_string()
|
||||
}
|
||||
CheckKind::SkipDocstring => {
|
||||
"Function decorated with @overload shouldn't contain a docstring".to_string()
|
||||
"Function decorated with `@overload` shouldn't contain a docstring".to_string()
|
||||
}
|
||||
CheckKind::CapitalizeSectionName(name) => {
|
||||
format!("Section name should be properly capitalized (\"{name}\")")
|
||||
@@ -1250,6 +1341,9 @@ impl CheckKind {
|
||||
CheckKind::InvalidFirstArgumentNameForMethod => {
|
||||
"First argument of a method should be named `self`".to_string()
|
||||
}
|
||||
CheckKind::NonLowercaseVariableInFunction(name) => {
|
||||
format!("Variable `{name}` in function should be lowercase")
|
||||
}
|
||||
CheckKind::DunderFunctionName => {
|
||||
"Function name should not start and end with `__`".to_string()
|
||||
}
|
||||
@@ -1265,9 +1359,18 @@ impl CheckKind {
|
||||
CheckKind::CamelcaseImportedAsConstant(name, asname) => {
|
||||
format!("Camelcase `{name}` imported as constant `{asname}`")
|
||||
}
|
||||
CheckKind::MixedCaseVariableInClassScope(name) => {
|
||||
format!("Variable `{name}` in class scope should not be mixedCase")
|
||||
}
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name) => {
|
||||
format!("Variable `{name}` in global scope should not be mixedCase")
|
||||
}
|
||||
CheckKind::CamelcaseImportedAsAcronym(name, asname) => {
|
||||
format!("Camelcase `{name}` imported as acronym `{asname}`")
|
||||
}
|
||||
CheckKind::ErrorSuffixOnExceptionName(name) => {
|
||||
format!("Exception name `{name}` should be named with an Error suffix")
|
||||
}
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
@@ -1338,7 +1441,7 @@ impl CheckKind {
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TypeOfPrimitive(_)
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnusedImport(_)
|
||||
| CheckKind::UnusedImport(_, false)
|
||||
| CheckKind::UnusedLoopControlVariable(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
| CheckKind::UsePEP585Annotation(_)
|
||||
|
||||
1106
src/checks_gen.rs
Normal file
1106
src/checks_gen.rs
Normal file
File diff suppressed because it is too large
Load Diff
28
src/cli.rs
28
src/cli.rs
@@ -5,11 +5,11 @@ use clap::{command, Parser};
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::pyproject::StrCheckCodePair;
|
||||
use crate::settings::PythonVersion;
|
||||
use crate::RawSettings;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::types::StrCheckCodePair;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
@@ -43,16 +43,16 @@ pub struct Cli {
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCode>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
@@ -106,10 +106,10 @@ impl fmt::Display for Warnable {
|
||||
/// Warn the user if they attempt to enable a code that won't be respected.
|
||||
pub fn warn_on(
|
||||
flag: Warnable,
|
||||
codes: &[CheckCode],
|
||||
cli_ignore: &[CheckCode],
|
||||
cli_extend_ignore: &[CheckCode],
|
||||
pyproject_settings: &RawSettings,
|
||||
codes: &[CheckCodePrefix],
|
||||
cli_ignore: &[CheckCodePrefix],
|
||||
cli_extend_ignore: &[CheckCodePrefix],
|
||||
pyproject_configuration: &Configuration,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
) {
|
||||
for code in codes {
|
||||
@@ -117,7 +117,7 @@ pub fn warn_on(
|
||||
if cli_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
|
||||
}
|
||||
} else if pyproject_settings.ignore.contains(code) {
|
||||
} else if pyproject_configuration.ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
|
||||
@@ -131,7 +131,7 @@ pub fn warn_on(
|
||||
if cli_extend_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
|
||||
}
|
||||
} else if pyproject_settings.extend_ignore.contains(code) {
|
||||
} else if pyproject_configuration.extend_ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
|
||||
|
||||
@@ -27,8 +27,8 @@ pub fn leading_space(line: &str) -> String {
|
||||
/// Extract the leading indentation from a docstring.
|
||||
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> &'a str {
|
||||
let range = Range::from_located(docstring);
|
||||
checker.get_locator().slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 1),
|
||||
checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ pub use assert_false::assert_false;
|
||||
pub use assert_raises_exception::assert_raises_exception;
|
||||
pub use duplicate_exceptions::duplicate_exceptions;
|
||||
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use unary_prefix_increment::unary_prefix_increment;
|
||||
pub use unused_loop_control_variable::unused_loop_control_variable;
|
||||
|
||||
mod assert_false;
|
||||
mod assert_raises_exception;
|
||||
mod duplicate_exceptions;
|
||||
mod mutable_argument_default;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod unary_prefix_increment;
|
||||
mod unused_loop_control_variable;
|
||||
|
||||
62
src/flake8_bugbear/plugins/mutable_argument_default.rs
Normal file
62
src/flake8_bugbear/plugins/mutable_argument_default.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use rustpython_ast::{Arguments, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B006
|
||||
pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
for expr in arguments
|
||||
.defaults
|
||||
.iter()
|
||||
.chain(arguments.kw_defaults.iter())
|
||||
{
|
||||
match &expr.node {
|
||||
ExprKind::List { .. }
|
||||
| ExprKind::Dict { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::SetComp { .. } => {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
ExprKind::Call { func, .. } => match &func.node {
|
||||
ExprKind::Name { id, .. }
|
||||
if id == "dict"
|
||||
|| id == "list"
|
||||
|| id == "set"
|
||||
|| id == "Counter"
|
||||
|| id == "OrderedDict"
|
||||
|| id == "defaultdict"
|
||||
|| id == "deque" =>
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. }
|
||||
if (attr == "Counter"
|
||||
|| attr == "OrderedDict"
|
||||
|| attr == "defaultdict"
|
||||
|| attr == "deque") =>
|
||||
{
|
||||
match &value.node {
|
||||
ExprKind::Name { id, .. } if id == "collections" => {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B013
|
||||
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
for handler in handlers {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
|
||||
if let Some(type_) = type_ {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
if elts.len() == 1 {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::RedundantTupleInExceptionHandler(elts[0].to_string()),
|
||||
checker.locate_check(Range::from_located(type_)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_ast::{Comprehension, Constant, Expr, ExprKind, KeywordData, Located, Unaryop};
|
||||
use rustpython_ast::{
|
||||
Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop,
|
||||
};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
@@ -16,7 +18,11 @@ fn exactly_one_argument_with_matching_function<'a>(
|
||||
name: &str,
|
||||
func: &Expr,
|
||||
args: &'a [Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<&'a ExprKind> {
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if args.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
@@ -37,9 +43,14 @@ fn first_argument_with_matching_function<'a>(
|
||||
Some(&args.first()?.node)
|
||||
}
|
||||
|
||||
/// Check `list(generator)` compliance.
|
||||
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("list", func, args)?;
|
||||
/// C400 (`list(generator)`)
|
||||
pub fn unnecessary_generator_list(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { .. } = argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorList,
|
||||
@@ -49,9 +60,14 @@ pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &[Expr]) -> Op
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set(generator)` compliance.
|
||||
pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
|
||||
/// C401 (`set(generator)`)
|
||||
pub fn unnecessary_generator_set(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { .. } = argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -61,9 +77,14 @@ pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Opt
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `dict((x, y) for x, y in iterable)` compliance.
|
||||
pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
|
||||
/// C402 (`dict((x, y) for x, y in iterable)`)
|
||||
pub fn unnecessary_generator_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { elt, .. } = argument {
|
||||
match &elt.node {
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
@@ -78,13 +99,14 @@ pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Op
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set([...])` compliance.
|
||||
/// C403 (`set([...])`)
|
||||
pub fn unnecessary_list_comprehension_set(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
if let ExprKind::ListComp { .. } = &argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionSet,
|
||||
@@ -94,13 +116,14 @@ pub fn unnecessary_list_comprehension_set(
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `dict([...])` compliance.
|
||||
/// C404 (`dict([...])`)
|
||||
pub fn unnecessary_list_comprehension_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
if let ExprKind::ListComp { elt, .. } = &argument {
|
||||
match &elt.node {
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
@@ -115,9 +138,14 @@ pub fn unnecessary_list_comprehension_dict(
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set([1, 2])` compliance.
|
||||
pub fn unnecessary_literal_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
|
||||
/// C405 (`set([1, 2])`)
|
||||
pub fn unnecessary_literal_set(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
let kind = match argument {
|
||||
ExprKind::List { .. } => "list",
|
||||
ExprKind::Tuple { .. } => "tuple",
|
||||
@@ -129,9 +157,14 @@ pub fn unnecessary_literal_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Optio
|
||||
))
|
||||
}
|
||||
|
||||
/// Check `dict([(1, 2)])` compliance.
|
||||
pub fn unnecessary_literal_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
|
||||
/// C406 (`dict([(1, 2)])`)
|
||||
pub fn unnecessary_literal_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
let (kind, elts) = match argument {
|
||||
ExprKind::Tuple { elts, .. } => ("tuple", elts),
|
||||
ExprKind::List { elts, .. } => ("list", elts),
|
||||
@@ -151,6 +184,7 @@ pub fn unnecessary_literal_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Opti
|
||||
))
|
||||
}
|
||||
|
||||
/// C408
|
||||
pub fn unnecessary_collection_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
@@ -162,10 +196,10 @@ pub fn unnecessary_collection_call(
|
||||
}
|
||||
let id = function_name(func)?;
|
||||
match id {
|
||||
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (),
|
||||
"list" | "tuple" => {
|
||||
// list() or tuple()
|
||||
}
|
||||
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (),
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
@@ -174,6 +208,7 @@ pub fn unnecessary_collection_call(
|
||||
))
|
||||
}
|
||||
|
||||
/// C409
|
||||
pub fn unnecessary_literal_within_tuple_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
@@ -191,6 +226,7 @@ pub fn unnecessary_literal_within_tuple_call(
|
||||
))
|
||||
}
|
||||
|
||||
/// C410
|
||||
pub fn unnecessary_literal_within_list_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
@@ -208,6 +244,7 @@ pub fn unnecessary_literal_within_list_call(
|
||||
))
|
||||
}
|
||||
|
||||
/// C411
|
||||
pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let argument = first_argument_with_matching_function("list", func, args)?;
|
||||
if let ExprKind::ListComp { .. } = argument {
|
||||
@@ -219,6 +256,7 @@ pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<
|
||||
None
|
||||
}
|
||||
|
||||
/// C413
|
||||
pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let outer = function_name(func)?;
|
||||
if !(outer == "list" || outer == "reversed") {
|
||||
@@ -235,6 +273,7 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
None
|
||||
}
|
||||
|
||||
/// C414
|
||||
pub fn unnecessary_double_cast_or_process(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
@@ -274,6 +313,7 @@ pub fn unnecessary_double_cast_or_process(
|
||||
None
|
||||
}
|
||||
|
||||
/// C415
|
||||
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
let first_arg = args.first()?;
|
||||
let id = function_name(func)?;
|
||||
@@ -309,6 +349,7 @@ pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
None
|
||||
}
|
||||
|
||||
/// C416
|
||||
pub fn unnecessary_comprehension(
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
@@ -337,6 +378,7 @@ pub fn unnecessary_comprehension(
|
||||
))
|
||||
}
|
||||
|
||||
/// C417
|
||||
pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
fn new_check(kind: &str, expr: &Expr) -> Check {
|
||||
Check::new(
|
||||
|
||||
298
src/flake8_quotes/checks.rs
Normal file
298
src/flake8_quotes/checks.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
use rustpython_ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_quotes::settings::{Quote, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn good_single(quote: &Quote) -> char {
|
||||
match quote {
|
||||
Quote::Single => '\'',
|
||||
Quote::Double => '"',
|
||||
}
|
||||
}
|
||||
|
||||
fn bad_single(quote: &Quote) -> char {
|
||||
match quote {
|
||||
Quote::Double => '\'',
|
||||
Quote::Single => '"',
|
||||
}
|
||||
}
|
||||
|
||||
fn good_multiline(quote: &Quote) -> &str {
|
||||
match quote {
|
||||
Quote::Single => "'''",
|
||||
Quote::Double => "\"\"\"",
|
||||
}
|
||||
}
|
||||
|
||||
fn good_multiline_ending(quote: &Quote) -> &str {
|
||||
match quote {
|
||||
Quote::Single => "'\"\"\"",
|
||||
Quote::Double => "\"'''",
|
||||
}
|
||||
}
|
||||
|
||||
fn good_docstring(quote: &Quote) -> &str {
|
||||
match quote {
|
||||
Quote::Single => "'''",
|
||||
Quote::Double => "\"\"\"",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quotes(
|
||||
locator: &SourceCodeLocator,
|
||||
start: &Location,
|
||||
end: &Location,
|
||||
is_docstring: bool,
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
let text = locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
});
|
||||
|
||||
// Remove any prefixes (e.g., remove `u` from `u"foo"`).
|
||||
let last_quote_char = text.chars().last().unwrap();
|
||||
let first_quote_char = text.find(last_quote_char).unwrap();
|
||||
let prefix = &text[..first_quote_char].to_lowercase();
|
||||
let raw_text = &text[first_quote_char..];
|
||||
|
||||
// Determine if the string is multiline-based.
|
||||
let is_multiline = if raw_text.len() >= 3 {
|
||||
let mut chars = raw_text.chars();
|
||||
let first = chars.next().unwrap();
|
||||
let second = chars.next().unwrap();
|
||||
let third = chars.next().unwrap();
|
||||
first == second && second == third
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_docstring {
|
||||
if raw_text.contains(good_docstring(&settings.docstring_quotes)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(Check::new(
|
||||
CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()),
|
||||
Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
},
|
||||
));
|
||||
} else if is_multiline {
|
||||
// If our string is or contains a known good string, ignore it.
|
||||
if raw_text.contains(good_multiline(&settings.multiline_quotes)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If our string ends with a known good ending, then ignore it.
|
||||
if raw_text.ends_with(good_multiline_ending(&settings.multiline_quotes)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(Check::new(
|
||||
CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()),
|
||||
Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
},
|
||||
));
|
||||
} else {
|
||||
let string_contents = &raw_text[1..raw_text.len() - 1];
|
||||
|
||||
// If we're using the preferred quotation type, check for escapes.
|
||||
if last_quote_char == good_single(&settings.inline_quotes) {
|
||||
if !settings.avoid_escape || prefix.contains('r') {
|
||||
return None;
|
||||
}
|
||||
if string_contents.contains(good_single(&settings.inline_quotes))
|
||||
&& !string_contents.contains(bad_single(&settings.inline_quotes))
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::AvoidQuoteEscape,
|
||||
Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
},
|
||||
));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// If we're not using the preferred type, only allow use to avoid escapes.
|
||||
if !string_contents.contains(good_single(&settings.inline_quotes)) {
|
||||
return Some(Check::new(
|
||||
CheckKind::BadQuotesInlineString(settings.inline_quotes.clone()),
|
||||
Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::linter::tokenize;
|
||||
use crate::{flake8_quotes, linter, Settings};
|
||||
use crate::{fs, noqa};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)
|
||||
}
|
||||
|
||||
#[test_case(Path::new("doubles.py"))]
|
||||
#[test_case(Path::new("doubles_escaped.py"))]
|
||||
#[test_case(Path::new("doubles_multiline_string.py"))]
|
||||
#[test_case(Path::new("doubles_noqa.py"))]
|
||||
#[test_case(Path::new("doubles_wrapped.py"))]
|
||||
fn doubles(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("doubles_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
flake8_quotes: flake8_quotes::settings::Settings {
|
||||
inline_quotes: Quote::Single,
|
||||
multiline_quotes: Quote::Single,
|
||||
docstring_quotes: Quote::Single,
|
||||
avoid_escape: true,
|
||||
},
|
||||
..Settings::for_rules(vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("singles.py"))]
|
||||
#[test_case(Path::new("singles_escaped.py"))]
|
||||
#[test_case(Path::new("singles_multiline_string.py"))]
|
||||
#[test_case(Path::new("singles_noqa.py"))]
|
||||
#[test_case(Path::new("singles_wrapped.py"))]
|
||||
fn singles(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("singles_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
flake8_quotes: flake8_quotes::settings::Settings {
|
||||
inline_quotes: Quote::Double,
|
||||
multiline_quotes: Quote::Double,
|
||||
docstring_quotes: Quote::Double,
|
||||
avoid_escape: true,
|
||||
},
|
||||
..Settings::for_rules(vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("docstring_doubles.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_class.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_function.py"))]
|
||||
#[test_case(Path::new("docstring_singles.py"))]
|
||||
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
|
||||
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
|
||||
#[test_case(Path::new("docstring_singles_class.py"))]
|
||||
#[test_case(Path::new("docstring_singles_function.py"))]
|
||||
fn double_docstring(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
flake8_quotes: flake8_quotes::settings::Settings {
|
||||
inline_quotes: Quote::Single,
|
||||
multiline_quotes: Quote::Single,
|
||||
docstring_quotes: Quote::Double,
|
||||
avoid_escape: true,
|
||||
},
|
||||
..Settings::for_rules(vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("docstring_doubles.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_class.py"))]
|
||||
#[test_case(Path::new("docstring_doubles_function.py"))]
|
||||
#[test_case(Path::new("docstring_singles.py"))]
|
||||
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
|
||||
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
|
||||
#[test_case(Path::new("docstring_singles_class.py"))]
|
||||
#[test_case(Path::new("docstring_singles_function.py"))]
|
||||
fn single_docstring(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
flake8_quotes: flake8_quotes::settings::Settings {
|
||||
inline_quotes: Quote::Single,
|
||||
multiline_quotes: Quote::Double,
|
||||
docstring_quotes: Quote::Single,
|
||||
avoid_escape: true,
|
||||
},
|
||||
..Settings::for_rules(vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
118
src/flake8_quotes/docstring_detection.rs
Normal file
118
src/flake8_quotes/docstring_detection.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! Extract docstrings via tokenization.
|
||||
//!
|
||||
//! See: https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29
|
||||
//!
|
||||
//! TODO(charlie): Consolidate with the existing AST-based docstring extraction.
|
||||
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
// Start of the module: first string gets marked as a docstring.
|
||||
ExpectModuleDocstring,
|
||||
// After seeing a class definition, we're waiting for the block colon (and do bracket counting).
|
||||
ExpectClassColon,
|
||||
// After seeing the block colon in a class definition, we expect a docstring.
|
||||
ExpectClassDocstring,
|
||||
// Same as ExpectClassColon, but for function definitions.
|
||||
ExpectFunctionColon,
|
||||
// Same as ExpectClassDocstring, but for function definitions.
|
||||
ExpectFunctionDocstring,
|
||||
// Skip tokens until we observe a `class` or `def`.
|
||||
Other,
|
||||
}
|
||||
|
||||
pub struct StateMachine {
|
||||
state: State,
|
||||
bracket_count: usize,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: State::ExpectModuleDocstring,
|
||||
bracket_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(&mut self, tok: &Tok) -> bool {
|
||||
if matches!(tok, Tok::Newline | Tok::Indent | Tok::Dedent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
return if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Class) {
|
||||
self.state = State::ExpectClassColon;
|
||||
self.bracket_count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Def) {
|
||||
self.state = State::ExpectFunctionColon;
|
||||
self.bracket_count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Colon) {
|
||||
if self.bracket_count == 0 {
|
||||
if matches!(self.state, State::ExpectClassColon) {
|
||||
self.state = State::ExpectClassDocstring;
|
||||
} else if matches!(self.state, State::ExpectFunctionColon) {
|
||||
self.state = State::ExpectFunctionDocstring;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Lpar | Tok::Lbrace | Tok::Lsqb) {
|
||||
self.bracket_count += 1;
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar | Tok::Rbrace | Tok::Rsqb) {
|
||||
self.bracket_count -= 1;
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
return false;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
3
src/flake8_quotes/mod.rs
Normal file
3
src/flake8_quotes/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod checks;
|
||||
pub mod docstring_detection;
|
||||
pub mod settings;
|
||||
49
src/flake8_quotes/settings.rs
Normal file
49
src/flake8_quotes/settings.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
//! Settings for the `flake-quotes` plugin.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub inline_quotes: Option<Quote>,
|
||||
pub multiline_quotes: Option<Quote>,
|
||||
pub docstring_quotes: Option<Quote>,
|
||||
pub avoid_escape: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub inline_quotes: Quote,
|
||||
pub multiline_quotes: Quote,
|
||||
pub docstring_quotes: Quote,
|
||||
pub avoid_escape: bool,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
inline_quotes: options.inline_quotes.unwrap_or(Quote::Single),
|
||||
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
|
||||
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
|
||||
avoid_escape: options.avoid_escape.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inline_quotes: Quote::Single,
|
||||
multiline_quotes: Quote::Double,
|
||||
docstring_quotes: Quote::Double,
|
||||
avoid_escape: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 16
|
||||
column: 4
|
||||
end_location:
|
||||
row: 18
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 21
|
||||
column: 20
|
||||
end_location:
|
||||
row: 22
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 30
|
||||
column: 8
|
||||
end_location:
|
||||
row: 32
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 35
|
||||
column: 12
|
||||
end_location:
|
||||
row: 37
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 5
|
||||
column: 22
|
||||
end_location:
|
||||
row: 5
|
||||
column: 43
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 15
|
||||
column: 38
|
||||
end_location:
|
||||
row: 17
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 17
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 21
|
||||
column: 4
|
||||
end_location:
|
||||
row: 21
|
||||
column: 27
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 3
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 26
|
||||
column: 8
|
||||
end_location:
|
||||
row: 28
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 6
|
||||
column: 8
|
||||
end_location:
|
||||
row: 6
|
||||
column: 57
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 9
|
||||
column: 28
|
||||
end_location:
|
||||
row: 9
|
||||
column: 52
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 56
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 3
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: double
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 49
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesInlineString: single
|
||||
location:
|
||||
row: 1
|
||||
column: 24
|
||||
end_location:
|
||||
row: 1
|
||||
column: 45
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesInlineString: single
|
||||
location:
|
||||
row: 2
|
||||
column: 24
|
||||
end_location:
|
||||
row: 2
|
||||
column: 46
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: AvoidQuoteEscape
|
||||
location:
|
||||
row: 1
|
||||
column: 25
|
||||
end_location:
|
||||
row: 1
|
||||
column: 47
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: single
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 14
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 24
|
||||
column: 8
|
||||
end_location:
|
||||
row: 26
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 6
|
||||
column: 8
|
||||
end_location:
|
||||
row: 6
|
||||
column: 57
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 9
|
||||
column: 28
|
||||
end_location:
|
||||
row: 9
|
||||
column: 52
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 56
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 3
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesDocstring: single
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 49
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 11
|
||||
column: 20
|
||||
end_location:
|
||||
row: 13
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 20
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 23
|
||||
column: 20
|
||||
end_location:
|
||||
row: 24
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 32
|
||||
column: 8
|
||||
end_location:
|
||||
row: 34
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 37
|
||||
column: 12
|
||||
end_location:
|
||||
row: 39
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 5
|
||||
column: 22
|
||||
end_location:
|
||||
row: 5
|
||||
column: 43
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 15
|
||||
column: 38
|
||||
end_location:
|
||||
row: 17
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 17
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 21
|
||||
column: 4
|
||||
end_location:
|
||||
row: 21
|
||||
column: 27
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 3
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesInlineString: double
|
||||
location:
|
||||
row: 1
|
||||
column: 24
|
||||
end_location:
|
||||
row: 1
|
||||
column: 45
|
||||
fix: ~
|
||||
- kind:
|
||||
BadQuotesInlineString: double
|
||||
location:
|
||||
row: 2
|
||||
column: 24
|
||||
end_location:
|
||||
row: 2
|
||||
column: 46
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: AvoidQuoteEscape
|
||||
location:
|
||||
row: 1
|
||||
column: 25
|
||||
end_location:
|
||||
row: 1
|
||||
column: 47
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BadQuotesMultilineString: double
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/flake8_quotes/checks.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -12,7 +12,7 @@ use path_absolutize::Absolutize;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::settings::{FilePattern, PerFileIgnore};
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore};
|
||||
|
||||
/// Extract the absolute path and basename (as strings) from a Path.
|
||||
fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
@@ -133,7 +133,7 @@ pub fn ignores_from_path<'a>(
|
||||
[&pattern_code_pair.pattern].into_iter(),
|
||||
)
|
||||
})
|
||||
.map(|pattern_code_pair| &pattern_code_pair.code)
|
||||
.flat_map(|pattern_code_pair| &pattern_code_pair.codes)
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ mod tests {
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::fs::{extract_path_names, is_excluded, is_included};
|
||||
use crate::settings::FilePattern;
|
||||
use crate::settings::types::FilePattern;
|
||||
|
||||
#[test]
|
||||
fn inclusions() {
|
||||
|
||||
36
src/lib.rs
36
src/lib.rs
@@ -6,18 +6,22 @@ use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use settings::pyproject;
|
||||
use settings::Settings;
|
||||
|
||||
use crate::autofix::fixer::Mode;
|
||||
use crate::checks::Check;
|
||||
use crate::linter::{check_path, tokenize};
|
||||
use crate::message::Message;
|
||||
use crate::settings::{RawSettings, Settings};
|
||||
use crate::settings::configuration::Configuration;
|
||||
|
||||
mod ast;
|
||||
mod autofix;
|
||||
pub mod autofix;
|
||||
pub mod cache;
|
||||
pub mod check_ast;
|
||||
mod check_lines;
|
||||
mod check_tokens;
|
||||
pub mod checks;
|
||||
mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
@@ -26,6 +30,7 @@ mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
mod flake8_print;
|
||||
mod flake8_quotes;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
@@ -36,14 +41,14 @@ pub mod printer;
|
||||
mod pycodestyle;
|
||||
mod pydocstyle;
|
||||
mod pyflakes;
|
||||
pub mod pyproject;
|
||||
mod python;
|
||||
mod pyupgrade;
|
||||
pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str, quiet: bool) -> Result<Vec<Message>> {
|
||||
pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
|
||||
match &project_root {
|
||||
@@ -56,11 +61,8 @@ pub fn check(path: &Path, contents: &str, quiet: bool) -> Result<Vec<Message>> {
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_raw(RawSettings::from_pyproject(
|
||||
&pyproject,
|
||||
&project_root,
|
||||
quiet,
|
||||
)?);
|
||||
let settings =
|
||||
Settings::from_configuration(Configuration::from_pyproject(&pyproject, &project_root)?);
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
@@ -78,17 +80,5 @@ pub fn check(path: &Path, contents: &str, quiet: bool) -> Result<Vec<Message>> {
|
||||
&Mode::None,
|
||||
)?;
|
||||
|
||||
// Convert to messages.
|
||||
let messages: Vec<Message> = checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(messages)
|
||||
Ok(checks)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ use std::path::Path;
|
||||
use anyhow::Result;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use log::debug;
|
||||
use rustpython_ast::{Mod, Suite};
|
||||
use rustpython_parser::error::ParseError;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::parser::Mode;
|
||||
use rustpython_parser::{lexer, parser};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -20,6 +23,7 @@ use crate::code_gen::SourceGenerator;
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{cache, fs, noqa};
|
||||
|
||||
/// Collect tokens up to and including the first error.
|
||||
@@ -35,6 +39,17 @@ pub(crate) fn tokenize(contents: &str) -> Vec<LexResult> {
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Parse a full Python program from its tokens.
|
||||
pub(crate) fn parse_program_tokens(
|
||||
lxr: Vec<LexResult>,
|
||||
source_path: &str,
|
||||
) -> Result<Suite, ParseError> {
|
||||
parser::parse_tokens(lxr, Mode::Module, source_path).map(|top| match top {
|
||||
Mod::Module { body, .. } => body,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
@@ -46,13 +61,16 @@ pub(crate) fn check_path(
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Run the token-based checks.
|
||||
if settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens))
|
||||
{
|
||||
check_tokens(&mut checks, contents, &tokens, settings);
|
||||
check_tokens(&mut checks, &locator, &tokens, settings);
|
||||
}
|
||||
|
||||
// Run the AST-based checks.
|
||||
@@ -61,9 +79,9 @@ pub(crate) fn check_path(
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
{
|
||||
match parser::parse_program_tokens(tokens, "<filename>") {
|
||||
match parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
|
||||
checks.extend(check_ast(&python_ast, &locator, settings, autofix, path))
|
||||
}
|
||||
Err(parse_error) => {
|
||||
if settings.enabled.contains(&CheckCode::E999) {
|
||||
@@ -216,7 +234,7 @@ pub fn autoformat_path(path: &Path) -> Result<()> {
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
|
||||
// Generate the AST.
|
||||
let python_ast = parser::parse_program_tokens(tokens, "<filename>")?;
|
||||
let python_ast = parse_program_tokens(tokens, "<filename>")?;
|
||||
let mut generator: SourceGenerator = Default::default();
|
||||
generator.unparse_suite(&python_ast)?;
|
||||
write(path, generator.generate()?)?;
|
||||
@@ -236,16 +254,12 @@ mod tests {
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::linter;
|
||||
use crate::linter::tokenize;
|
||||
use crate::settings;
|
||||
use crate::{fs, noqa};
|
||||
use crate::{linter, Settings};
|
||||
|
||||
fn check_path(
|
||||
path: &Path,
|
||||
settings: &settings::Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
@@ -256,8 +270,10 @@ mod tests {
|
||||
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
|
||||
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
||||
#[test_case(CheckCode::B002, Path::new("B002.py"); "B002")]
|
||||
#[test_case(CheckCode::B006, Path::new("B006_B008.py"); "B006")]
|
||||
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
|
||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||
@@ -372,12 +388,16 @@ mod tests {
|
||||
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
|
||||
#[test_case(CheckCode::N804, Path::new("N804.py"); "N804")]
|
||||
#[test_case(CheckCode::N805, Path::new("N805.py"); "N805")]
|
||||
#[test_case(CheckCode::N806, Path::new("N806.py"); "N806")]
|
||||
#[test_case(CheckCode::N807, Path::new("N807.py"); "N807")]
|
||||
#[test_case(CheckCode::N811, Path::new("N811.py"); "N811")]
|
||||
#[test_case(CheckCode::N812, Path::new("N812.py"); "N812")]
|
||||
#[test_case(CheckCode::N813, Path::new("N813.py"); "N813")]
|
||||
#[test_case(CheckCode::N814, Path::new("N814.py"); "N814")]
|
||||
#[test_case(CheckCode::N815, Path::new("N815.py"); "N815")]
|
||||
#[test_case(CheckCode::N816, Path::new("N816.py"); "N816")]
|
||||
#[test_case(CheckCode::N817, Path::new("N817.py"); "N817")]
|
||||
#[test_case(CheckCode::N818, Path::new("N818.py"); "N818")]
|
||||
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
|
||||
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
|
||||
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]
|
||||
|
||||
45
src/main.rs
45
src/main.rs
@@ -25,10 +25,11 @@ use ruff::linter::{lint_path, lint_stdin};
|
||||
use ruff::logging::set_up_logging;
|
||||
use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
use ruff::pyproject::{self};
|
||||
use ruff::settings::CurrentSettings;
|
||||
use ruff::settings::RawSettings;
|
||||
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
|
||||
use ruff::settings::configuration::Configuration;
|
||||
use ruff::settings::pyproject;
|
||||
use ruff::settings::types::{FilePattern, PerFileIgnore};
|
||||
use ruff::settings::user::UserConfiguration;
|
||||
use ruff::settings::Settings;
|
||||
use ruff::tell_user;
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
@@ -73,10 +74,14 @@ fn check_for_updates() {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
|
||||
fn show_settings(
|
||||
configuration: Configuration,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) {
|
||||
println!(
|
||||
"{:#?}",
|
||||
CurrentSettings::from_settings(settings, project_root, pyproject)
|
||||
UserConfiguration::from_configuration(configuration, project_root, pyproject)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -256,15 +261,15 @@ fn inner_main() -> Result<ExitCode> {
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
|
||||
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root, cli.quiet)?;
|
||||
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
settings.exclude = exclude;
|
||||
configuration.exclude = exclude;
|
||||
}
|
||||
if !extend_exclude.is_empty() {
|
||||
settings.extend_exclude = extend_exclude;
|
||||
configuration.extend_exclude = extend_exclude;
|
||||
}
|
||||
if !per_file_ignores.is_empty() {
|
||||
settings.per_file_ignores = per_file_ignores;
|
||||
configuration.per_file_ignores = per_file_ignores;
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
warn_on(
|
||||
@@ -272,10 +277,10 @@ fn inner_main() -> Result<ExitCode> {
|
||||
&cli.select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&configuration,
|
||||
&pyproject,
|
||||
);
|
||||
settings.select = cli.select;
|
||||
configuration.select = cli.select;
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
warn_on(
|
||||
@@ -283,22 +288,22 @@ fn inner_main() -> Result<ExitCode> {
|
||||
&cli.extend_select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&configuration,
|
||||
&pyproject,
|
||||
);
|
||||
settings.extend_select = cli.extend_select;
|
||||
configuration.extend_select = cli.extend_select;
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore = cli.ignore;
|
||||
configuration.ignore = cli.ignore;
|
||||
}
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
settings.extend_ignore = cli.extend_ignore;
|
||||
configuration.extend_ignore = cli.extend_ignore;
|
||||
}
|
||||
if let Some(target_version) = cli.target_version {
|
||||
settings.target_version = target_version;
|
||||
configuration.target_version = target_version;
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
|
||||
settings.dummy_variable_rgx = dummy_variable_rgx;
|
||||
configuration.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
@@ -306,11 +311,11 @@ fn inner_main() -> Result<ExitCode> {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(settings, project_root, pyproject);
|
||||
show_settings(configuration, project_root, pyproject);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
let settings = Settings::from_raw(settings);
|
||||
let settings = Settings::from_configuration(configuration);
|
||||
|
||||
if cli.show_files {
|
||||
show_files(&cli.files, &settings);
|
||||
|
||||
24
src/noqa.rs
24
src/noqa.rs
@@ -169,7 +169,6 @@ y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
@@ -179,7 +178,6 @@ y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
@@ -189,7 +187,6 @@ z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
@@ -200,7 +197,6 @@ z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
@@ -250,8 +246,8 @@ ghi
|
||||
let checks = vec![Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
)];
|
||||
let contents = "x = 1";
|
||||
@@ -264,15 +260,15 @@ ghi
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
),
|
||||
];
|
||||
@@ -286,15 +282,15 @@ ghi
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::types::{Range, Scope, ScopeKind};
|
||||
use crate::ast::types::{FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
|
||||
/// N801
|
||||
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
|
||||
let stripped = name.strip_prefix('_').unwrap_or(name);
|
||||
if !stripped
|
||||
@@ -21,8 +23,14 @@ pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
|
||||
if name.chars().any(|c| c.is_uppercase()) {
|
||||
/// N802
|
||||
pub fn invalid_function_name(func_def: &Stmt, name: &str, settings: &Settings) -> Option<Check> {
|
||||
if !is_lower(name)
|
||||
&& !settings
|
||||
.ignore_names
|
||||
.iter()
|
||||
.any(|ignore_name| ignore_name == name)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::InvalidFunctionName(name.to_string()),
|
||||
Range::from_located(func_def),
|
||||
@@ -31,8 +39,9 @@ pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// N803
|
||||
pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
|
||||
if name.chars().any(|c| c.is_uppercase()) {
|
||||
if !is_lower(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::InvalidArgumentName(name.to_string()),
|
||||
location,
|
||||
@@ -41,10 +50,12 @@ pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// N804
|
||||
pub fn invalid_first_argument_name_for_class_method(
|
||||
scope: &Scope,
|
||||
decorator_list: &[Expr],
|
||||
args: &Arguments,
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
@@ -52,7 +63,7 @@ pub fn invalid_first_argument_name_for_class_method(
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
id == "classmethod"
|
||||
settings.classmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -69,10 +80,12 @@ pub fn invalid_first_argument_name_for_class_method(
|
||||
None
|
||||
}
|
||||
|
||||
/// N805
|
||||
pub fn invalid_first_argument_name_for_method(
|
||||
scope: &Scope,
|
||||
decorator_list: &[Expr],
|
||||
args: &Arguments,
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
@@ -80,7 +93,8 @@ pub fn invalid_first_argument_name_for_method(
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
id == "classmethod" || id == "staticmethod"
|
||||
settings.classmethod_decorators.contains(id)
|
||||
|| settings.staticmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -99,6 +113,21 @@ pub fn invalid_first_argument_name_for_method(
|
||||
None
|
||||
}
|
||||
|
||||
/// N806
|
||||
pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
|
||||
return None;
|
||||
}
|
||||
if !is_lower(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N807
|
||||
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
|
||||
if matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
@@ -114,6 +143,136 @@ pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Optio
|
||||
None
|
||||
}
|
||||
|
||||
/// N811
|
||||
pub fn constant_imported_as_non_constant(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_upper(name) && !is_upper(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N812
|
||||
pub fn lowercase_imported_as_non_lowercase(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_lower(name) && asname.to_lowercase() != asname {
|
||||
return Some(Check::new(
|
||||
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N813
|
||||
pub fn camelcase_imported_as_lowercase(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_lower(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N814
|
||||
pub fn camelcase_imported_as_constant(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N815
|
||||
pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
}
|
||||
if is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInClassScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N816
|
||||
pub fn mixed_case_variable_in_global_scope(
|
||||
scope: &Scope,
|
||||
expr: &Expr,
|
||||
name: &str,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Module) {
|
||||
return None;
|
||||
}
|
||||
if is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N817
|
||||
pub fn camelcase_imported_as_acronym(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N818
|
||||
pub fn error_suffix_on_exception_name(
|
||||
class_def: &Stmt,
|
||||
bases: &[Expr],
|
||||
name: &str,
|
||||
) -> Option<Check> {
|
||||
if bases.iter().any(|base| {
|
||||
if let ExprKind::Name { id, .. } = &base.node {
|
||||
id == "Exception"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
if !name.ends_with("Error") {
|
||||
return Some(Check::new(
|
||||
CheckKind::ErrorSuffixOnExceptionName(name.to_string()),
|
||||
Range::from_located(class_def),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_lower(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
@@ -138,86 +297,27 @@ fn is_upper(s: &str) -> bool {
|
||||
cased
|
||||
}
|
||||
|
||||
pub fn constant_imported_as_non_constant(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_upper(name) && !is_upper(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn lowercase_imported_as_non_lowercase(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_lower(name) && asname.to_lowercase() != asname {
|
||||
return Some(Check::new(
|
||||
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_camelcase(name: &str) -> bool {
|
||||
!is_lower(name) && !is_upper(name) && !name.contains('_')
|
||||
}
|
||||
|
||||
fn is_mixed_case(name: &str) -> bool {
|
||||
!is_lower(name)
|
||||
&& name
|
||||
.strip_prefix('_')
|
||||
.unwrap_or(name)
|
||||
.chars()
|
||||
.next()
|
||||
.map_or_else(|| false, |c| c.is_lowercase())
|
||||
}
|
||||
|
||||
fn is_acronym(name: &str, asname: &str) -> bool {
|
||||
name.chars().filter(|c| c.is_uppercase()).join("") == asname
|
||||
}
|
||||
|
||||
pub fn camelcase_imported_as_lowercase(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_lower(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn camelcase_imported_as_constant(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn camelcase_imported_as_acronym(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_acronym, is_camelcase, is_lower, is_upper};
|
||||
use super::{is_acronym, is_camelcase, is_lower, is_mixed_case, is_upper};
|
||||
|
||||
#[test]
|
||||
fn test_is_lower() -> () {
|
||||
@@ -251,6 +351,17 @@ mod tests {
|
||||
assert!(!is_camelcase("CAMEL_CASE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_mixed_case() -> () {
|
||||
assert!(is_mixed_case("mixedCase"));
|
||||
assert!(is_mixed_case("mixed_Case"));
|
||||
assert!(is_mixed_case("_mixed_Case"));
|
||||
assert!(!is_mixed_case("mixed_case"));
|
||||
assert!(!is_mixed_case("MIXED_CASE"));
|
||||
assert!(!is_mixed_case(""));
|
||||
assert!(!is_mixed_case("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_acronym() -> () {
|
||||
assert!(is_acronym("AB", "AB"));
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod checks;
|
||||
pub mod settings;
|
||||
|
||||
63
src/pep8_naming/settings.rs
Normal file
63
src/pep8_naming/settings.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Settings for the `pep8-naming` plugin.
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
const IGNORE_NAMES: [&str; 12] = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
];
|
||||
|
||||
const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
|
||||
|
||||
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub ignore_names: Option<Vec<String>>,
|
||||
pub classmethod_decorators: Option<Vec<String>>,
|
||||
pub staticmethod_decorators: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub ignore_names: Vec<String>,
|
||||
pub classmethod_decorators: Vec<String>,
|
||||
pub staticmethod_decorators: Vec<String>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
ignore_names: options
|
||||
.ignore_names
|
||||
.unwrap_or_else(|| IGNORE_NAMES.map(String::from).to_vec()),
|
||||
classmethod_decorators: options
|
||||
.classmethod_decorators
|
||||
.unwrap_or_else(|| CLASSMETHOD_DECORATORS.map(String::from).to_vec()),
|
||||
staticmethod_decorators: options
|
||||
.staticmethod_decorators
|
||||
.unwrap_or_else(|| STATICMETHOD_DECORATORS.map(String::from).to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ignore_names: IGNORE_NAMES.map(String::from).to_vec(),
|
||||
classmethod_decorators: CLASSMETHOD_DECORATORS.map(String::from).to_vec(),
|
||||
staticmethod_decorators: STATICMETHOD_DECORATORS.map(String::from).to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
use itertools::izip;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Unaryop};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::checks::{Check, CheckKind, RejectedCmpop};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn is_ambiguous_name(name: &str) -> bool {
|
||||
name == "l" || name == "I" || name == "O"
|
||||
}
|
||||
|
||||
/// Check AmbiguousVariableName compliance.
|
||||
/// E741
|
||||
pub fn ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
@@ -20,7 +22,7 @@ pub fn ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousClassName compliance.
|
||||
/// E742
|
||||
pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
@@ -32,7 +34,7 @@ pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check AmbiguousFunctionName compliance.
|
||||
/// E743
|
||||
pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
@@ -44,7 +46,7 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check DoNotAssignLambda compliance.
|
||||
/// E731
|
||||
pub fn do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Lambda { .. } = &value.node {
|
||||
Some(Check::new(CheckKind::DoNotAssignLambda, location))
|
||||
@@ -53,7 +55,7 @@ pub fn do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check NotInTest and NotIsTest compliance.
|
||||
/// E713, E714
|
||||
pub fn not_tests(
|
||||
op: &Unaryop,
|
||||
operand: &Expr,
|
||||
@@ -92,7 +94,7 @@ pub fn not_tests(
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check TrueFalseComparison and NoneComparison compliance.
|
||||
/// E711, E712
|
||||
pub fn literal_comparisons(
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
@@ -201,7 +203,7 @@ pub fn literal_comparisons(
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check TypeComparison compliance.
|
||||
/// E721
|
||||
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -236,3 +238,92 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) ->
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
|
||||
const VALID_ESCAPE_SEQUENCES: &[char; 23] = &[
|
||||
'\n', '\\', '\'', '"', 'a', 'b', 'f', 'n', 'r', 't', 'v', '0', '1', '2', '3', '4', '5', '6',
|
||||
'7', 'x', // Escape sequences only recognized in string literals
|
||||
'N', 'u', 'U',
|
||||
];
|
||||
|
||||
/// Return the quotation markers used for a String token.
|
||||
fn extract_quote(text: &str) -> &str {
|
||||
for quote in ["'''", "\"\"\"", "'", "\""] {
|
||||
if text.ends_with(quote) {
|
||||
return quote;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Unable to find quotation mark for String token.")
|
||||
}
|
||||
|
||||
/// W605
|
||||
pub fn invalid_escape_sequence(
|
||||
locator: &SourceCodeLocator,
|
||||
start: &Location,
|
||||
end: &Location,
|
||||
) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
|
||||
let text = locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
});
|
||||
|
||||
// Determine whether the string is single- or triple-quoted.
|
||||
let quote = extract_quote(text);
|
||||
let quote_pos = text.find(quote).unwrap();
|
||||
let prefix = text[..quote_pos].to_lowercase();
|
||||
let body = &text[(quote_pos + quote.len())..(text.len() - quote.len())];
|
||||
|
||||
if !prefix.contains('r') {
|
||||
let mut col_offset = 0;
|
||||
let mut row_offset = 0;
|
||||
let mut in_escape = false;
|
||||
let mut chars = body.chars();
|
||||
let mut current = chars.next();
|
||||
let mut next = chars.next();
|
||||
while let (Some(current_char), Some(next_char)) = (current, next) {
|
||||
// If we see an escaped backslash, avoid treating the character _after_ the
|
||||
// escaped backslash as itself an escaped character.
|
||||
if in_escape {
|
||||
in_escape = false;
|
||||
} else {
|
||||
in_escape = current_char == '\\' && next_char == '\\';
|
||||
if current_char == '\\' && !VALID_ESCAPE_SEQUENCES.contains(&next_char) {
|
||||
// Compute the location of the escape sequence by offsetting the location of the
|
||||
// string token by the characters we've seen thus far.
|
||||
let location = if row_offset == 0 {
|
||||
Location::new(
|
||||
start.row() + row_offset,
|
||||
start.column() + prefix.len() + quote.len() + col_offset,
|
||||
)
|
||||
} else {
|
||||
Location::new(start.row() + row_offset, col_offset + 1)
|
||||
};
|
||||
let end_location = Location::new(location.row(), location.column() + 1);
|
||||
checks.push(Check::new(
|
||||
CheckKind::InvalidEscapeSequence(next_char),
|
||||
Range {
|
||||
location,
|
||||
end_location,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Track the offset from the start position as we iterate over the body.
|
||||
if current_char == '\n' {
|
||||
col_offset = 0;
|
||||
row_offset += 1;
|
||||
} else {
|
||||
col_offset += 1;
|
||||
}
|
||||
|
||||
current = next;
|
||||
next = chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ pub fn not_missing(
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicModule,
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -47,8 +47,8 @@ pub fn not_missing(
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicPackage,
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 0),
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -162,7 +162,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
} = &docstring.node
|
||||
{
|
||||
if checker.settings.enabled.contains(&CheckCode::D201) {
|
||||
let (before, _, _) = checker.get_locator().partition_source_code_at(
|
||||
let (before, _, _) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -181,8 +181,8 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
if checker.patch() {
|
||||
// Delete the blank line before the docstring.
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(docstring.location.row() - blank_lines_before, 1),
|
||||
Location::new(docstring.location.row(), 1),
|
||||
Location::new(docstring.location.row() - blank_lines_before, 0),
|
||||
Location::new(docstring.location.row(), 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -190,7 +190,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D202) {
|
||||
let (_, _, after) = checker.get_locator().partition_source_code_at(
|
||||
let (_, _, after) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -208,15 +208,13 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
// Report a D202 violation if the docstring is followed by a blank line and the
|
||||
// blank line is not itself followed by an inner function or class.
|
||||
let expected_blank_lines_after =
|
||||
if INNER_FUNCTION_OR_CLASS_REGEX.is_match(after) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if blank_lines_after != expected_blank_lines_after {
|
||||
|
||||
// Avoid D202 violations for blank lines followed by inner functions or classes.
|
||||
if blank_lines_after == 1 && INNER_FUNCTION_OR_CLASS_REGEX.is_match(after) {
|
||||
return;
|
||||
}
|
||||
|
||||
if blank_lines_after != 0 {
|
||||
let mut check = Check::new(
|
||||
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
|
||||
Range::from_located(docstring),
|
||||
@@ -224,11 +222,8 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
if checker.patch() {
|
||||
// Delete the blank line after the docstring.
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(
|
||||
docstring.location.row() + 1 + expected_blank_lines_after,
|
||||
1,
|
||||
),
|
||||
Location::new(docstring.location.row() + 1 + blank_lines_after, 1),
|
||||
Location::new(docstring.location.row() + 1, 0),
|
||||
Location::new(docstring.location.row() + 1 + blank_lines_after, 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -253,7 +248,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
if checker.settings.enabled.contains(&CheckCode::D203)
|
||||
|| checker.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
let (before, _, _) = checker.get_locator().partition_source_code_at(
|
||||
let (before, _, _) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -273,8 +268,8 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
if checker.patch() {
|
||||
// Delete the blank line before the class.
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(docstring.location.row() - blank_lines_before, 1),
|
||||
Location::new(docstring.location.row(), 1),
|
||||
Location::new(docstring.location.row() - blank_lines_before, 0),
|
||||
Location::new(docstring.location.row(), 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -290,8 +285,8 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
// Insert one blank line before the class.
|
||||
check.amend(Fix::replacement(
|
||||
"\n".to_string(),
|
||||
Location::new(docstring.location.row() - blank_lines_before, 1),
|
||||
Location::new(docstring.location.row(), 1),
|
||||
Location::new(docstring.location.row() - blank_lines_before, 0),
|
||||
Location::new(docstring.location.row(), 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -300,7 +295,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D204) {
|
||||
let (_, _, after) = checker.get_locator().partition_source_code_at(
|
||||
let (_, _, after) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -327,10 +322,10 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
// Insert a blank line before the class (replacing any existing lines).
|
||||
check.amend(Fix::replacement(
|
||||
"\n".to_string(),
|
||||
Location::new(docstring.end_location.unwrap().row() + 1, 1),
|
||||
Location::new(docstring.end_location.unwrap().row() + 1, 0),
|
||||
Location::new(
|
||||
docstring.end_location.unwrap().row() + 1 + blank_lines_after,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -369,8 +364,8 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
|
||||
// Insert one blank line after the summary (replacing any existing lines).
|
||||
check.amend(Fix::replacement(
|
||||
"\n".to_string(),
|
||||
Location::new(docstring.location.row() + 1, 1),
|
||||
Location::new(docstring.location.row() + 1 + blanks_count, 1),
|
||||
Location::new(docstring.location.row() + 1, 0),
|
||||
Location::new(docstring.location.row() + 1 + blanks_count, 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -404,7 +399,8 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
|
||||
// Omit empty lines, except for the last line, which is non-empty by way of
|
||||
// containing the closing quotation marks.
|
||||
if i < lines.len() - 1 && lines[i].trim().is_empty() {
|
||||
let is_blank = lines[i].trim().is_empty();
|
||||
if i < lines.len() - 1 && is_blank {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -416,19 +412,19 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
if checker.settings.enabled.contains(&CheckCode::D207) {
|
||||
// We report under-indentation on every line. This isn't great, but enables
|
||||
// autofix.
|
||||
if line_indent.len() < docstring_indent.len() {
|
||||
if !is_blank && line_indent.len() < docstring_indent.len() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::NoUnderIndentation,
|
||||
Range {
|
||||
location: Location::new(docstring.location.row() + i, 1),
|
||||
end_location: Location::new(docstring.location.row() + i, 1),
|
||||
location: Location::new(docstring.location.row() + i, 0),
|
||||
end_location: Location::new(docstring.location.row() + i, 0),
|
||||
},
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
helpers::clean(&docstring_indent),
|
||||
Location::new(docstring.location.row() + i, 1),
|
||||
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
|
||||
Location::new(docstring.location.row() + i, 0),
|
||||
Location::new(docstring.location.row() + i, line_indent.len()),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -469,18 +465,15 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::NoOverIndentation,
|
||||
Range {
|
||||
location: Location::new(docstring.location.row() + i, 1),
|
||||
end_location: Location::new(docstring.location.row() + i, 1),
|
||||
location: Location::new(docstring.location.row() + i, 0),
|
||||
end_location: Location::new(docstring.location.row() + i, 0),
|
||||
},
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
helpers::clean(&docstring_indent),
|
||||
Location::new(docstring.location.row() + i, 1),
|
||||
Location::new(
|
||||
docstring.location.row() + i,
|
||||
1 + line_indent.len(),
|
||||
),
|
||||
Location::new(docstring.location.row() + i, 0),
|
||||
Location::new(docstring.location.row() + i, line_indent.len()),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -496,15 +489,15 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::NoOverIndentation,
|
||||
Range {
|
||||
location: Location::new(docstring.location.row() + i, 1),
|
||||
end_location: Location::new(docstring.location.row() + i, 1),
|
||||
location: Location::new(docstring.location.row() + i, 0),
|
||||
end_location: Location::new(docstring.location.row() + i, 0),
|
||||
},
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
helpers::clean(&docstring_indent),
|
||||
Location::new(docstring.location.row() + i, 1),
|
||||
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
|
||||
Location::new(docstring.location.row() + i, 0),
|
||||
Location::new(docstring.location.row() + i, line_indent.len()),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -530,7 +523,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
|
||||
}
|
||||
if line_count > 1 {
|
||||
let content = checker
|
||||
.get_locator()
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(docstring));
|
||||
if let Some(last_line) = content.lines().last().map(|line| line.trim()) {
|
||||
if last_line != "\"\"\"" && last_line != "'''" {
|
||||
@@ -583,7 +576,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
|
||||
);
|
||||
if checker.patch() {
|
||||
if let Some(first_line) = checker
|
||||
.get_locator()
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
@@ -629,7 +622,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
|
||||
{
|
||||
if string.lines().nth(1).is_some() {
|
||||
if let Some(first_line) = checker
|
||||
.get_locator()
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
@@ -665,7 +658,7 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(first_line) = checker
|
||||
.get_locator()
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
@@ -925,7 +918,7 @@ fn blanks_and_section_underline(
|
||||
);
|
||||
check.amend(Fix::insertion(
|
||||
content,
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 1),
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -959,7 +952,7 @@ fn blanks_and_section_underline(
|
||||
);
|
||||
check.amend(Fix::insertion(
|
||||
content,
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 1),
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -975,13 +968,13 @@ fn blanks_and_section_underline(
|
||||
if checker.patch() {
|
||||
// Delete any blank lines between the header and content.
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 1),
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 0),
|
||||
Location::new(
|
||||
docstring.location.row()
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ blank_lines_after_header,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -998,13 +991,13 @@ fn blanks_and_section_underline(
|
||||
if checker.patch() {
|
||||
// Delete any blank lines between the header and the underline.
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 1),
|
||||
Location::new(docstring.location.row() + context.original_index + 1, 0),
|
||||
Location::new(
|
||||
docstring.location.row()
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ blank_lines_after_header,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -1040,7 +1033,7 @@ fn blanks_and_section_underline(
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ blank_lines_after_header,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row()
|
||||
@@ -1048,7 +1041,7 @@ fn blanks_and_section_underline(
|
||||
+ 1
|
||||
+ blank_lines_after_header
|
||||
+ 1,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
};
|
||||
@@ -1073,7 +1066,7 @@ fn blanks_and_section_underline(
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ blank_lines_after_header,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row()
|
||||
@@ -1121,7 +1114,7 @@ fn blanks_and_section_underline(
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ line_after_dashes_index,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row()
|
||||
@@ -1129,7 +1122,7 @@ fn blanks_and_section_underline(
|
||||
+ 1
|
||||
+ line_after_dashes_index
|
||||
+ blank_lines_after_dashes,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -1183,11 +1176,11 @@ fn common_section(
|
||||
capitalized_section_name,
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + section_name_start,
|
||||
*section_name_start,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + section_name_start + section_name_length,
|
||||
section_name_start + section_name_length,
|
||||
),
|
||||
))
|
||||
}
|
||||
@@ -1209,10 +1202,10 @@ fn common_section(
|
||||
// Replace the existing indentation with whitespace of the appropriate length.
|
||||
check.amend(Fix::replacement(
|
||||
helpers::clean(&indentation),
|
||||
Location::new(docstring.location.row() + context.original_index, 1),
|
||||
Location::new(docstring.location.row() + context.original_index, 0),
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + leading_space.len(),
|
||||
leading_space.len(),
|
||||
),
|
||||
));
|
||||
};
|
||||
@@ -1241,7 +1234,7 @@ fn common_section(
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ context.following_lines.len(),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -1262,7 +1255,7 @@ fn common_section(
|
||||
+ context.original_index
|
||||
+ 1
|
||||
+ context.following_lines.len(),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -1281,7 +1274,7 @@ fn common_section(
|
||||
// Add a blank line before the section.
|
||||
check.amend(Fix::insertion(
|
||||
"\n".to_string(),
|
||||
Location::new(docstring.location.row() + context.original_index, 1),
|
||||
Location::new(docstring.location.row() + context.original_index, 0),
|
||||
));
|
||||
}
|
||||
checker.add_check(check)
|
||||
@@ -1448,11 +1441,11 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + suffix_start,
|
||||
*suffix_start,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + suffix_start + suffix_length,
|
||||
suffix_start + suffix_length,
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -1498,11 +1491,11 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect
|
||||
":".to_string(),
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + suffix_start,
|
||||
*suffix_start,
|
||||
),
|
||||
Location::new(
|
||||
docstring.location.row() + context.original_index,
|
||||
1 + suffix_start + suffix_length,
|
||||
suffix_start + suffix_length,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use rustpython_parser::ast::{
|
||||
use crate::ast::types::{BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
/// F634
|
||||
pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
@@ -20,7 +20,7 @@ pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Check AssertTuple compliance.
|
||||
/// F631
|
||||
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
@@ -30,7 +30,7 @@ pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Check UnusedVariable compliance.
|
||||
/// F841
|
||||
pub fn unused_variables(
|
||||
scope: &Scope,
|
||||
locator: &dyn CheckLocator,
|
||||
@@ -63,7 +63,7 @@ pub fn unused_variables(
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check DefaultExceptNotLast compliance.
|
||||
/// F707
|
||||
pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
|
||||
for (idx, handler) in handlers.iter().enumerate() {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
|
||||
@@ -78,7 +78,7 @@ pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Check RaiseNotImplemented compliance.
|
||||
/// F901
|
||||
pub fn raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
@@ -105,7 +105,7 @@ pub fn raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Check DuplicateArgumentName compliance.
|
||||
/// F831
|
||||
pub fn duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -153,7 +153,7 @@ fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check MultiValueRepeatedKeyLiteral and MultiValueRepeatedKeyVariable compliance.
|
||||
/// F601, F602
|
||||
pub fn repeated_keys(
|
||||
keys: &[Expr],
|
||||
check_repeated_literals: bool,
|
||||
@@ -215,7 +215,7 @@ fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// Check IsLiteral compliance.
|
||||
/// F632
|
||||
pub fn is_literal(left: &Expr, ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -232,7 +232,7 @@ pub fn is_literal(left: &Expr, ops: &[Cmpop], comparators: &[Expr], location: Ra
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
|
||||
/// F621, F622
|
||||
pub fn starred_expressions(
|
||||
elts: &[Expr],
|
||||
check_too_many_expressions: bool,
|
||||
@@ -262,7 +262,7 @@ pub fn starred_expressions(
|
||||
None
|
||||
}
|
||||
|
||||
/// Check BreakOutsideLoop compliance.
|
||||
/// F701
|
||||
pub fn break_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
@@ -303,7 +303,7 @@ pub fn break_outside_loop(
|
||||
}
|
||||
}
|
||||
|
||||
/// Check ContinueOutsideLoop compliance.
|
||||
/// F702
|
||||
pub fn continue_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
|
||||
@@ -2,10 +2,10 @@ use anyhow::Result;
|
||||
use libcst_native::{Codegen, ImportNames, NameOrAttribute, SmallStatement, Statement};
|
||||
use rustpython_ast::Stmt;
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{helpers, Fix};
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Generate a Fix to remove any unused imports from an `import` statement.
|
||||
pub fn remove_unused_imports(
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ast::types::{Binding, BindingKind, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pyupgrade::types::Primitive;
|
||||
|
||||
/// Check that `super()` has no args
|
||||
/// U008
|
||||
pub fn super_args(
|
||||
scope: &Scope,
|
||||
parents: &[&Stmt],
|
||||
@@ -70,7 +70,7 @@ pub fn super_args(
|
||||
None
|
||||
}
|
||||
|
||||
/// Check UselessMetaclassType compliance.
|
||||
/// U001
|
||||
pub fn useless_metaclass_type(targets: &[Expr], value: &Expr, location: Range) -> Option<Check> {
|
||||
if targets.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
|
||||
@@ -86,7 +86,7 @@ pub fn useless_metaclass_type(targets: &[Expr], value: &Expr, location: Range) -
|
||||
None
|
||||
}
|
||||
|
||||
/// Check UnnecessaryAbspath compliance.
|
||||
/// U002
|
||||
pub fn unnecessary_abspath(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
@@ -106,7 +106,7 @@ pub fn unnecessary_abspath(func: &Expr, args: &[Expr], location: Range) -> Optio
|
||||
None
|
||||
}
|
||||
|
||||
/// Check UselessObjectInheritance compliance.
|
||||
/// U004
|
||||
pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) -> Option<Check> {
|
||||
for expr in bases {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
@@ -131,7 +131,7 @@ pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) ->
|
||||
None
|
||||
}
|
||||
|
||||
/// Check TypeOfPrimitive compliance.
|
||||
/// U003
|
||||
pub fn type_of_primitive(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
|
||||
@@ -4,9 +4,9 @@ use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Generate a fix to remove a base from a ClassDef statement.
|
||||
pub fn remove_class_def_base(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user