Compare commits
131 Commits
blacklist_
...
v0.0.261
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
255b094b33 | ||
|
|
b6155232ac | ||
|
|
390d7dcf39 | ||
|
|
251340a246 | ||
|
|
d919adc13c | ||
|
|
46bcb1f725 | ||
|
|
2b21effa77 | ||
|
|
10504eb9ed | ||
|
|
76e111c874 | ||
|
|
e006b922a6 | ||
|
|
60f6a8571a | ||
|
|
f4173b2a93 | ||
|
|
449e08ed08 | ||
|
|
5625410936 | ||
|
|
3744e9ab3f | ||
|
|
b52cb93e58 | ||
|
|
849091d846 | ||
|
|
d2f2544f6e | ||
|
|
25771cd4b9 | ||
|
|
924bebbb4a | ||
|
|
08e5b3fa61 | ||
|
|
d822e08111 | ||
|
|
2f90157ce2 | ||
|
|
88308ef9cc | ||
|
|
6d80c79bac | ||
|
|
2fbc620ad3 | ||
|
|
27e40e9b31 | ||
|
|
b6276e2d95 | ||
|
|
66d72b1c7b | ||
|
|
968c7df770 | ||
|
|
fe38597279 | ||
|
|
f3f9a9f297 | ||
|
|
48d8680e71 | ||
|
|
82584ad101 | ||
|
|
13e52b1f76 | ||
|
|
dfc872c9a0 | ||
|
|
cf7e1ddd08 | ||
|
|
9de1f82658 | ||
|
|
54ad9397e5 | ||
|
|
29c8b75fd4 | ||
|
|
0b586d5451 | ||
|
|
01357f62e5 | ||
|
|
d7113d3995 | ||
|
|
a142d71e0b | ||
|
|
f79506f5a4 | ||
|
|
44ae3237b8 | ||
|
|
f4cda31708 | ||
|
|
4328448a2f | ||
|
|
88298759ce | ||
|
|
3c0e789b19 | ||
|
|
8601dcc09b | ||
|
|
134fdd1609 | ||
|
|
2e6eddc7bd | ||
|
|
cb588d1d6d | ||
|
|
9d3b8eb67b | ||
|
|
e1e5532ab1 | ||
|
|
7d962bf80c | ||
|
|
595cd065f3 | ||
|
|
b6f1fed424 | ||
|
|
5501fc9572 | ||
|
|
5977862a60 | ||
|
|
224e85c6d7 | ||
|
|
515e436cfa | ||
|
|
f322bcd2bd | ||
|
|
22d5b0071d | ||
|
|
990b378c4d | ||
|
|
e88fbae926 | ||
|
|
81de3a16bc | ||
|
|
bfecf684ce | ||
|
|
756e9956a2 | ||
|
|
f68c26a506 | ||
|
|
000394f428 | ||
|
|
2fdf98ef4e | ||
|
|
1d724b1495 | ||
|
|
113a8b8fda | ||
|
|
c3917eab38 | ||
|
|
0eb5a22dd1 | ||
|
|
450c6780ff | ||
|
|
5cb120327c | ||
|
|
8dbffb576d | ||
|
|
31fff4b10e | ||
|
|
2326335f5c | ||
|
|
6ed6da3e82 | ||
|
|
cd75b57036 | ||
|
|
e603382cf0 | ||
|
|
32be63fd1e | ||
|
|
d594179275 | ||
|
|
c0befb4670 | ||
|
|
a66481ed28 | ||
|
|
5c7898124f | ||
|
|
50a7916e84 | ||
|
|
6a40a5c5a2 | ||
|
|
fec4fa39a7 | ||
|
|
2659336ed1 | ||
|
|
8ac7584756 | ||
|
|
4a1740a4c4 | ||
|
|
2083134a96 | ||
|
|
c721eedc37 | ||
|
|
c1d89d8c93 | ||
|
|
b8ae1e0e05 | ||
|
|
63adf9f5e8 | ||
|
|
7af83460ce | ||
|
|
dc4d7619ee | ||
|
|
1bac206995 | ||
|
|
efc6e8cb39 | ||
|
|
7f3b748401 | ||
|
|
7da06b9741 | ||
|
|
0f95056f13 | ||
|
|
028329854b | ||
|
|
ba43d6bd0b | ||
|
|
e8d17d23cb | ||
|
|
aea925a898 | ||
|
|
f58345dee3 | ||
|
|
71c0da27bb | ||
|
|
8a2d1a3029 | ||
|
|
6161e56ea4 | ||
|
|
189c9d4683 | ||
|
|
615887a7fe | ||
|
|
07808a58f2 | ||
|
|
fe568c08d2 | ||
|
|
7741d43ae5 | ||
|
|
1b3e54231c | ||
|
|
3a8e98341b | ||
|
|
8593739f88 | ||
|
|
242dd3dae1 | ||
|
|
875f61cb62 | ||
|
|
3ec1ea8ac2 | ||
|
|
1e45b13958 | ||
|
|
9e61956711 | ||
|
|
5eae3fbbfb | ||
|
|
41e38ffa98 |
@@ -26,4 +26,11 @@ rustflags = [
|
||||
"-Wclippy::print_stdout",
|
||||
"-Wclippy::print_stderr",
|
||||
"-Wclippy::dbg_macro",
|
||||
"-Wclippy::empty_drop",
|
||||
"-Wclippy::empty_structs_with_brackets",
|
||||
"-Wclippy::exit",
|
||||
"-Wclippy::get_unwrap",
|
||||
"-Wclippy::rc_buffer",
|
||||
"-Wclippy::rc_mutex",
|
||||
"-Wclippy::rest_pat_in_fully_bound_structs",
|
||||
]
|
||||
|
||||
62
.github/workflows/ci.yaml
vendored
62
.github/workflows/ci.yaml
vendored
@@ -85,7 +85,6 @@ jobs:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
|
||||
|
||||
cargo-test-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
@@ -111,14 +110,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
run: rustup component add rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
|
||||
- run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
|
||||
- run: cargo check
|
||||
- run: cargo fmt --all --check
|
||||
- run: |
|
||||
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
|
||||
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
|
||||
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
|
||||
- run: cargo check
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
typos:
|
||||
name: "spell check"
|
||||
@@ -176,3 +177,56 @@ jobs:
|
||||
with:
|
||||
name: ecosystem-result
|
||||
path: ecosystem-result
|
||||
|
||||
cargo-udeps:
|
||||
name: "cargo udeps"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install nightly Rust toolchain"
|
||||
# Only pinned to make caching work, update freely
|
||||
run: rustup toolchain install nightly-2023-03-30
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Install cargo-udeps"
|
||||
uses: taiki-e/install-action@cargo-udeps
|
||||
- name: "Run cargo-udeps"
|
||||
run: |
|
||||
unused_dependencies=$(cargo +nightly-2023-03-30 udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
|
||||
if [ -z "$unused_dependencies" ]; then
|
||||
echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
else
|
||||
echo "Found unused dependencies" > $GITHUB_STEP_SUMMARY
|
||||
echo '```console' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Install pre-commit"
|
||||
run: pip install pre-commit
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: "Run pre-commit"
|
||||
run: |
|
||||
echo '```console' > $GITHUB_STEP_SUMMARY
|
||||
# Enable color output for pre-commit and remove it for the summary
|
||||
SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always | \
|
||||
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> $GITHUB_STEP_SUMMARY) >&1
|
||||
exit_code=${PIPESTATUS[0]}
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit $exit_code
|
||||
|
||||
59
Cargo.lock
generated
59
Cargo.lock
generated
@@ -132,12 +132,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bisection"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "021e079a1bab0ecce6cf4b4b74c0c37afa4a697136eb3b127875c84a8f04a8c3"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
@@ -780,7 +774,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.257"
|
||||
version = "0.0.261"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.1.8",
|
||||
@@ -1549,8 +1543,9 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/konstin/pep440-rs.git?rev=a8fef4ec47f4c25b070b39cdbe6a0b9847e49941#a8fef4ec47f4c25b070b39cdbe6a0b9847e49941"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5daf676dd9ff1a39faf9c9da9c46f0dbb6211b21a1839a749f5510c24ceca3f"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
@@ -1982,10 +1977,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.257"
|
||||
version = "0.0.261"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bisection",
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"clap 4.1.8",
|
||||
@@ -2016,6 +2010,7 @@ dependencies = [
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_rustpython",
|
||||
"rustc-hash",
|
||||
@@ -2033,6 +2028,8 @@ dependencies = [
|
||||
"textwrap",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2063,7 +2060,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.257"
|
||||
version = "0.0.261"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2091,6 +2088,7 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
@@ -2102,6 +2100,7 @@ dependencies = [
|
||||
"tikv-jemallocator",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
"wild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2131,6 +2130,8 @@ dependencies = [
|
||||
name = "ruff_diagnostics"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"ruff_python_ast",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
@@ -2170,16 +2171,15 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_rustpython",
|
||||
"rustc-hash",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -2195,7 +2195,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ruff_formatter",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_rustpython",
|
||||
"ruff_testing_macros",
|
||||
"ruff_text_size",
|
||||
@@ -2206,6 +2205,20 @@ dependencies = [
|
||||
"test-case",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"is-macro",
|
||||
"nohash-hasher",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"rustc-hash",
|
||||
"rustpython-parser",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
@@ -2254,6 +2267,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff",
|
||||
"ruff_diagnostics",
|
||||
"ruff_python_ast",
|
||||
"ruff_rustpython",
|
||||
"rustpython-parser",
|
||||
@@ -2916,6 +2930,12 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
@@ -3262,6 +3282,15 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
|
||||
|
||||
[[package]]
|
||||
name = "wild"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
||||
@@ -24,6 +24,7 @@ is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
|
||||
log = { version = "0.4.17" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
@@ -40,6 +41,7 @@ serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1" }
|
||||
smallvec = { version = "1.10.0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
syn = { version = "1.0.109" }
|
||||
@@ -49,8 +51,6 @@ toml = { version = "0.7.2" }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
|
||||
9
LICENSE
9
LICENSE
@@ -195,6 +195,15 @@ are:
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-gettext, licensed as follows:
|
||||
"""
|
||||
BSD Zero Clause License
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-implicit-str-concat, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
25
README.md
25
README.md
@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.257'
|
||||
rev: 'v0.0.261'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -145,6 +145,20 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
|
||||
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
|
||||
|
||||
Ruff can also be used as a [GitHub Action](https://github.com/features/actions) via
|
||||
[`ruff-action`](https://github.com/chartboost/ruff-action):
|
||||
|
||||
```yaml
|
||||
name: Ruff
|
||||
on: [ push, pull_request ]
|
||||
jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: chartboost/ruff-action@v1
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
|
||||
@@ -160,7 +174,7 @@ select = ["E", "F"]
|
||||
ignore = []
|
||||
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["A", "B", "C", "D", "E", "F", "..."]
|
||||
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
||||
unfixable = []
|
||||
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
@@ -243,11 +257,12 @@ quality tools, including:
|
||||
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
- [flake8-django](https://pypi.org/project/flake8-django/)
|
||||
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
- [flake8-executable](https://pypi.org/project/flake8-executable/)
|
||||
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
|
||||
@@ -362,6 +377,10 @@ Ruff is used in a number of major open-source projects, including:
|
||||
- [nox](https://github.com/wntrblm/nox)
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [The Algorithms](https://github.com/TheAlgorithms/Python)
|
||||
- [Openverse](https://github.com/WordPress/openverse)
|
||||
- [MegaLinter](https://github.com/oxsecurity/megalinter)
|
||||
- [LangChain](https://github.com/hwchase17/langchain)
|
||||
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.257"
|
||||
version = "0.0.261"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.257"
|
||||
version = "0.0.261"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -18,11 +18,11 @@ ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_semantic = { path = "../ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_rustpython = { path = "../ruff_rustpython" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bisection = { version = "0.1.0" }
|
||||
bitflags = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
||||
@@ -38,7 +38,7 @@ itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
nohash-hasher = { workspace = true }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { workspace = true }
|
||||
@@ -47,9 +47,7 @@ path-absolutize = { workspace = true, features = [
|
||||
"use_unix_paths_on_wasm",
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { git = "https://github.com/konstin/pep440-rs.git", features = [
|
||||
"serde",
|
||||
], rev = "a8fef4ec47f4c25b070b39cdbe6a0b9847e49941" }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
@@ -58,14 +56,16 @@ rustpython-parser = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
smallvec = { version = "1.10.0" }
|
||||
smallvec = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
thiserror = { version = "1.0.38" }
|
||||
toml = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { version = "0.1.10" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true, features = ["yaml", "redactions"] }
|
||||
@@ -74,4 +74,5 @@ test-case = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
logical_lines = []
|
||||
jupyter_notebook = []
|
||||
|
||||
@@ -5,3 +5,4 @@ uses-*
|
||||
rewrite-*
|
||||
prefer-*
|
||||
consider-*
|
||||
use-*
|
||||
|
||||
3
crates/ruff/resources/test/fixtures/flake8_bandit/S301.py
vendored
Normal file
3
crates/ruff/resources/test/fixtures/flake8_bandit/S301.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import pickle
|
||||
|
||||
pickle.loads()
|
||||
3
crates/ruff/resources/test/fixtures/flake8_bandit/S312.py
vendored
Normal file
3
crates/ruff/resources/test/fixtures/flake8_bandit/S312.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from telnetlib import Telnet
|
||||
|
||||
Telnet("localhost", 23)
|
||||
@@ -1,7 +1,9 @@
|
||||
import collections
|
||||
import datetime as dt
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
import operator
|
||||
from pathlib import Path
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
@@ -165,6 +167,26 @@ def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
|
||||
pass
|
||||
|
||||
|
||||
# Allow decimals
|
||||
def decimal_okay(value=Decimal("0.1")):
|
||||
pass
|
||||
|
||||
# Allow dates
|
||||
def date_okay(value=dt.date(2023, 3, 27)):
|
||||
pass
|
||||
|
||||
# Allow datetimes
|
||||
def datetime_okay(value=dt.datetime(2023, 3, 27, 13, 51, 59)):
|
||||
pass
|
||||
|
||||
# Allow timedeltas
|
||||
def timedelta_okay(value=dt.timedelta(hours=1)):
|
||||
pass
|
||||
|
||||
# Allow paths
|
||||
def path_okay(value=Path(".")):
|
||||
pass
|
||||
|
||||
# B006 and B008
|
||||
# We should handle arbitrary nesting of these B008.
|
||||
def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
|
||||
@@ -57,3 +57,9 @@ def foo3():
|
||||
|
||||
def foo4():
|
||||
...
|
||||
|
||||
|
||||
def foo5():
|
||||
foo.bar # Attribute (raise)
|
||||
object().__class__ # Attribute (raise)
|
||||
"foo" + "bar" # BinOp (raise)
|
||||
|
||||
@@ -28,6 +28,11 @@ except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *(RuntimeError, TypeError)): # ok
|
||||
@@ -38,10 +43,36 @@ try:
|
||||
except (ValueError, *[RuntimeError, *(TypeError,)]): # ok
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *b): # ok
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(RuntimeError, TypeError)): # ok
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(b, c)): # ok
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(*b, *c)): # ok
|
||||
pass
|
||||
|
||||
|
||||
def what_to_catch():
|
||||
return ...
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except what_to_catch(): # ok
|
||||
pass
|
||||
pass
|
||||
|
||||
162
crates/ruff/resources/test/fixtures/flake8_bugbear/B031.py
vendored
Normal file
162
crates/ruff/resources/test/fixtures/flake8_bugbear/B031.py
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
import itertools
|
||||
from itertools import groupby
|
||||
|
||||
shoppers = ["Jane", "Joe", "Sarah"]
|
||||
items = [
|
||||
("lettuce", "greens"),
|
||||
("tomatoes", "greens"),
|
||||
("cucumber", "greens"),
|
||||
("chicken breast", "meats & fish"),
|
||||
("salmon", "meats & fish"),
|
||||
("ice cream", "frozen items"),
|
||||
]
|
||||
|
||||
carts = {shopper: [] for shopper in shoppers}
|
||||
|
||||
|
||||
def collect_shop_items(shopper, items):
|
||||
# Imagine this an expensive database query or calculation that is
|
||||
# advantageous to batch.
|
||||
carts[shopper] += items
|
||||
|
||||
|
||||
# Invoking the `groupby` function directly
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
for shopper in shoppers:
|
||||
shopper = shopper.title()
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
# We're outside the nested loop and used the group again.
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
collect_shop_items("Jane", section_items)
|
||||
collect_shop_items("Joe", section_items) # B031
|
||||
|
||||
|
||||
# Make sure to detect in other loop constructs as well - `while` loop
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
countdown = 3
|
||||
while countdown > 0:
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
countdown -= 1
|
||||
|
||||
# Make sure to detect in other loop constructs as well - `list` comprehension
|
||||
collection = []
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
collection.append([list(section_items) for _ in range(3)]) # B031
|
||||
|
||||
unique_items = set()
|
||||
another_set = set()
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
# For nested loops, it should not flag the usage of the name
|
||||
for item in section_items:
|
||||
unique_items.add(item)
|
||||
|
||||
# But it should be detected when used again
|
||||
for item in section_items: # B031
|
||||
another_set.add(item)
|
||||
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
# Variable has been overridden, skip checking
|
||||
section_items = list(unique_items)
|
||||
collect_shop_items("Jane", section_items)
|
||||
collect_shop_items("Jane", section_items)
|
||||
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
# Variable has been overridden, skip checking
|
||||
# Not a realistic situation, just for testing purpose
|
||||
(section_items := list(unique_items))
|
||||
collect_shop_items("Jane", section_items)
|
||||
collect_shop_items("Jane", section_items)
|
||||
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
# This is ok
|
||||
collect_shop_items("Jane", section_items)
|
||||
|
||||
# Invocation via the `itertools` module
|
||||
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
|
||||
for shopper in shoppers:
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
else:
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
|
||||
# Mutually exclusive branches shouldn't trigger the warning
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
else:
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
elif _section == "frozen items":
|
||||
# Mix `match` and `if` statements
|
||||
match shopper:
|
||||
case "Jane":
|
||||
collect_shop_items(shopper, section_items)
|
||||
if _section == "fourth":
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
case _:
|
||||
collect_shop_items(shopper, section_items)
|
||||
else:
|
||||
collect_shop_items(shopper, section_items)
|
||||
# Now, it should detect
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
|
||||
# Mutually exclusive branches shouldn't trigger the warning
|
||||
match _section:
|
||||
case "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
match shopper:
|
||||
case "Jane":
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
case _:
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
case "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
case _:
|
||||
collect_shop_items(shopper, section_items)
|
||||
# Now, it should detect
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
for group in groupby(items, key=lambda p: p[1]):
|
||||
# This is bad, but not detected currently
|
||||
collect_shop_items("Jane", group[1])
|
||||
collect_shop_items("Joe", group[1])
|
||||
|
||||
|
||||
# Make sure we ignore - but don't fail on more complicated invocations
|
||||
for _key, (_value1, _value2) in groupby(
|
||||
[("a", (1, 2)), ("b", (3, 4)), ("a", (5, 6))], key=lambda p: p[1]
|
||||
):
|
||||
collect_shop_items("Jane", group[1])
|
||||
collect_shop_items("Joe", group[1])
|
||||
|
||||
# Make sure we ignore - but don't fail on more complicated invocations
|
||||
for (_key1, _key2), (_value1, _value2) in groupby(
|
||||
[(("a", "a"), (1, 2)), (("b", "b"), (3, 4)), (("a", "a"), (5, 6))],
|
||||
key=lambda p: p[1],
|
||||
):
|
||||
collect_shop_items("Jane", group[1])
|
||||
collect_shop_items("Joe", group[1])
|
||||
|
||||
|
||||
# Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
# NOTE: This should always be at the end of the file.
|
||||
def groupby(data, key=None):
|
||||
pass
|
||||
|
||||
|
||||
for name, group in groupby(items):
|
||||
collect_shop_items("Jane", items)
|
||||
# This shouldn't be flagged as the `groupby` function is different
|
||||
collect_shop_items("Joe", items)
|
||||
113
crates/ruff/resources/test/fixtures/flake8_django/DJ012.py
vendored
Normal file
113
crates/ruff/resources/test/fixtures/flake8_django/DJ012.py
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
from django.db import models
|
||||
from django.db.models import Model
|
||||
|
||||
|
||||
class StrBeforeRandomField(models.Model):
|
||||
"""Model with `__str__` before a random property."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return ""
|
||||
|
||||
random_property = "foo"
|
||||
|
||||
|
||||
class StrBeforeFieldModel(models.Model):
|
||||
"""Model with `__str__` before fields."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
|
||||
class ManagerBeforeField(models.Model):
|
||||
"""Model with manager before fields."""
|
||||
|
||||
objects = "manager"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
|
||||
class CustomMethodBeforeStr(models.Model):
|
||||
"""Model with a custom method before `__str__`."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def my_method(self):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "foobar"
|
||||
|
||||
|
||||
class GetAbsoluteUrlBeforeSave(Model):
|
||||
"""Model with `get_absolute_url` method before `save` method.
|
||||
|
||||
Subclass this directly using the `Model` class.
|
||||
"""
|
||||
|
||||
def get_absolute_url(self):
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
|
||||
class ConstantsAreNotFields(models.Model):
|
||||
"""Model with an assignment to a constant after `__str__`."""
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
pass
|
||||
|
||||
MY_CONSTANT = id(1)
|
||||
|
||||
|
||||
class PerfectlyFine(models.Model):
|
||||
"""Model which has everything in perfect order."""
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
last_name = models.CharField(max_length=32)
|
||||
objects = "manager"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
verbose_name_plural = "tests"
|
||||
|
||||
def __str__(self):
|
||||
return "Perfectly fine!"
|
||||
|
||||
def save(self, **kwargs):
|
||||
super(PerfectlyFine, self).save(**kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "http://%s" % self
|
||||
|
||||
def my_method(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def random_property(self):
|
||||
return "%s" % self
|
||||
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT001.py
vendored
Normal file
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT001.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_(f"{'value'}")
|
||||
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT002.py
vendored
Normal file
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT002.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_("{}".format("line"))
|
||||
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT003.py
vendored
Normal file
1
crates/ruff/resources/test/fixtures/flake8_gettext/INT003.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_("%s" % "line")
|
||||
@@ -1,3 +1,9 @@
|
||||
import logging
|
||||
import logging as foo
|
||||
|
||||
logging.info("Hello {}".format("World!"))
|
||||
logging.log(logging.INFO, "Hello {}".format("World!"))
|
||||
foo.info("Hello {}".format("World!"))
|
||||
logging.log(logging.INFO, msg="Hello {}".format("World!"))
|
||||
logging.log(level=logging.INFO, msg="Hello {}".format("World!"))
|
||||
logging.log(msg="Hello {}".format("World!"), level=logging.INFO)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
|
||||
logging.info("Hello %s" % "World!")
|
||||
logging.log(logging.INFO, "Hello %s" % "World!")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
|
||||
logging.info("Hello" + " " + "World!")
|
||||
logging.log(logging.INFO, "Hello" + " " + "World!")
|
||||
|
||||
@@ -2,3 +2,4 @@ import logging
|
||||
|
||||
name = "world"
|
||||
logging.info(f"Hello {name}")
|
||||
logging.log(logging.INFO, f"Hello {name}")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
from distutils import log
|
||||
|
||||
logging.warn("Hello World!")
|
||||
log.warn("Hello world!") # This shouldn't be considered as a logger candidate
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
# no error
|
||||
all((x.id for x in bar))
|
||||
all(x.id for x in bar)
|
||||
all(x.id for x in bar)
|
||||
any(x.id for x in bar)
|
||||
any({x.id for x in bar})
|
||||
|
||||
# PIE 802
|
||||
# PIE802
|
||||
any([x.id for x in bar])
|
||||
all([x.id for x in bar])
|
||||
any( # first comment
|
||||
@@ -14,3 +7,13 @@ any( # first comment
|
||||
all( # first comment
|
||||
[x.id for x in bar], # second comment
|
||||
) # third comment
|
||||
any({x.id for x in bar})
|
||||
|
||||
# OK
|
||||
all(x.id for x in bar)
|
||||
all(x.id for x in bar)
|
||||
any(x.id for x in bar)
|
||||
all((x.id for x in bar))
|
||||
|
||||
async def f() -> bool:
|
||||
return all([await use_greeting(greeting) for greeting in await greetings()])
|
||||
|
||||
@@ -1,79 +1,161 @@
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
from math import inf
|
||||
|
||||
import numpy as np
|
||||
|
||||
def f12(
|
||||
x,
|
||||
y: str = os.pathsep, # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f11(*, x: str = "x") -> None: # OK
|
||||
...
|
||||
|
||||
|
||||
) -> None: ...
|
||||
def f11(*, x: str = "x") -> None: ... # OK
|
||||
def f13(
|
||||
x: list[str] = [
|
||||
x: list[str] = [ # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
] # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
]
|
||||
) -> None: ...
|
||||
def f14(
|
||||
x: tuple[str, ...] = (
|
||||
x: tuple[str, ...] = ( # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
) # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
)
|
||||
) -> None: ...
|
||||
def f15(
|
||||
x: set[str] = {
|
||||
x: set[str] = { # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
} # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
|
||||
...
|
||||
|
||||
|
||||
}
|
||||
) -> None: ...
|
||||
def f151(x: dict[int, int] = {1: 2}) -> None: ... # Ok
|
||||
def f152(
|
||||
x: dict[
|
||||
int, int
|
||||
] = { # OK
|
||||
1: 2,
|
||||
**{3: 4},
|
||||
}
|
||||
) -> None: ...
|
||||
def f153(
|
||||
x: list[
|
||||
int
|
||||
] = [ # OK
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
]
|
||||
) -> None: ...
|
||||
def f154(
|
||||
x: tuple[
|
||||
str, tuple[str, ...]
|
||||
] = ( # OK
|
||||
"foo",
|
||||
("bar", "baz"),
|
||||
)
|
||||
) -> None: ...
|
||||
def f141(
|
||||
x: list[
|
||||
int
|
||||
] = [ # OK
|
||||
*range(10)
|
||||
],
|
||||
) -> None: ...
|
||||
def f142(
|
||||
x: list[
|
||||
int
|
||||
] = list( # OK
|
||||
range(10)
|
||||
),
|
||||
) -> None: ...
|
||||
def f16(
|
||||
x: frozenset[
|
||||
bytes
|
||||
] = frozenset( # OK
|
||||
{b"foo", b"bar", b"baz"}
|
||||
)
|
||||
) -> None: ...
|
||||
def f17(
|
||||
x: str = "foo" + "bar", # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
x: str = "foo" # OK
|
||||
+ "bar",
|
||||
) -> None: ...
|
||||
def f18(
|
||||
x: str = b"foo" + b"bar", # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
x: str = b"foo" # OK
|
||||
+ b"bar",
|
||||
) -> None: ...
|
||||
def f19(
|
||||
x: object = "foo" + 4, # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
x: object = "foo" # OK
|
||||
+ 4,
|
||||
) -> None: ...
|
||||
def f20(
|
||||
x: int = 5 + 5, # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
x: int = 5
|
||||
+ 5, # OK
|
||||
) -> None: ...
|
||||
def f21(
|
||||
x: complex = 3j - 3j, # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
x: complex = 3j
|
||||
- 3j, # OK
|
||||
) -> None: ...
|
||||
def f22(
|
||||
x: complex = -42.5j + 4.3j, # OK
|
||||
) -> None:
|
||||
...
|
||||
x: complex = -42.5j # OK
|
||||
+ 4.3j,
|
||||
) -> None: ...
|
||||
def f23(
|
||||
x: bool = True, # OK
|
||||
) -> None: ...
|
||||
def f24(
|
||||
x: float = 3.14, # OK
|
||||
) -> None: ...
|
||||
def f25(
|
||||
x: float = -3.14, # OK
|
||||
) -> None: ...
|
||||
def f26(
|
||||
x: complex = -3.14j, # OK
|
||||
) -> None: ...
|
||||
def f27(
|
||||
x: complex = -3 - 3.14j, # OK
|
||||
) -> None: ...
|
||||
def f28(
|
||||
x: float = math.tau, # OK
|
||||
) -> None: ...
|
||||
def f29(
|
||||
x: float = math.inf, # OK
|
||||
) -> None: ...
|
||||
def f30(
|
||||
x: float = -math.inf, # OK
|
||||
) -> None: ...
|
||||
def f31(
|
||||
x: float = inf, # OK
|
||||
) -> None: ...
|
||||
def f32(
|
||||
x: float = np.inf, # OK
|
||||
) -> None: ...
|
||||
def f33(
|
||||
x: float = math.nan, # OK
|
||||
) -> None: ...
|
||||
def f34(
|
||||
x: float = -math.nan, # OK
|
||||
) -> None: ...
|
||||
def f35(
|
||||
x: complex = math.inf # OK
|
||||
+ 1j,
|
||||
) -> None: ...
|
||||
def f36(
|
||||
*,
|
||||
x: str = sys.version, # OK
|
||||
) -> None: ...
|
||||
def f37(
|
||||
*,
|
||||
x: str = "" # OK
|
||||
+ "",
|
||||
) -> None: ...
|
||||
|
||||
@@ -11,32 +11,74 @@ def f12(
|
||||
) -> None: ...
|
||||
def f11(*, x: str = "x") -> None: ... # OK
|
||||
def f13(
|
||||
x: list[
|
||||
str
|
||||
] = [ # Error PYI011 Only simple default values allowed for typed arguments
|
||||
x: list[str] = [ # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
]
|
||||
) -> None: ...
|
||||
def f14(
|
||||
x: tuple[
|
||||
str, ...
|
||||
] = ( # Error PYI011 Only simple default values allowed for typed arguments
|
||||
x: tuple[str, ...] = ( # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
)
|
||||
) -> None: ...
|
||||
def f15(
|
||||
x: set[
|
||||
str
|
||||
] = { # Error PYI011 Only simple default values allowed for typed arguments
|
||||
x: set[str] = { # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
}
|
||||
) -> None: ...
|
||||
def f151(x: dict[int, int] = {1: 2}) -> None: ... # Ok
|
||||
def f152(
|
||||
x: dict[
|
||||
int, int
|
||||
] = { # Error PYI011 Only simple default values allowed for typed arguments
|
||||
1: 2,
|
||||
**{3: 4},
|
||||
}
|
||||
) -> None: ...
|
||||
def f153(
|
||||
x: list[
|
||||
int
|
||||
] = [ # Error PYI011 Only simple default values allowed for typed arguments
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
]
|
||||
) -> None: ...
|
||||
def f154(
|
||||
x: tuple[
|
||||
str, tuple[str, ...]
|
||||
] = ( # Error PYI011 Only simple default values allowed for typed arguments
|
||||
"foo",
|
||||
("bar", "baz"),
|
||||
)
|
||||
) -> None: ...
|
||||
def f141(
|
||||
x: list[
|
||||
int
|
||||
] = [ # Error PYI011 Only simple default values allowed for typed arguments
|
||||
*range(10)
|
||||
],
|
||||
) -> None: ...
|
||||
def f142(
|
||||
x: list[
|
||||
int
|
||||
] = list( # Error PYI011 Only simple default values allowed for typed arguments
|
||||
range(10)
|
||||
),
|
||||
) -> None: ...
|
||||
def f16(
|
||||
x: frozenset[
|
||||
bytes
|
||||
@@ -109,8 +151,11 @@ def f35(
|
||||
+ 1j,
|
||||
) -> None: ...
|
||||
def f36(
|
||||
*, x: str = sys.version, # OK
|
||||
*,
|
||||
x: str = sys.version, # OK
|
||||
) -> None: ...
|
||||
def f37(
|
||||
*, x: str = "" + "", # Error PYI011 Only simple default values allowed for typed arguments
|
||||
*,
|
||||
x: str = "" # Error PYI011 Only simple default values allowed for typed arguments
|
||||
+ "",
|
||||
) -> None: ...
|
||||
|
||||
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI012.py
vendored
Normal file
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI012.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# Violations of PYI012
|
||||
|
||||
|
||||
class OneAttributeClass:
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
|
||||
class OneAttributeClassRev:
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
value: int
|
||||
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains pass.
|
||||
"""
|
||||
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
value: int
|
||||
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
|
||||
# Not violations (of PYI012)
|
||||
|
||||
|
||||
class EmptyClass:
|
||||
pass # Y009 Empty body should contain `...`, not `pass`
|
||||
|
||||
|
||||
class EmptyOneLine:
|
||||
pass # Y009 Empty body should contain `...`, not `pass`
|
||||
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
|
||||
class EmptyEllipsis:
|
||||
...
|
||||
|
||||
|
||||
class NonEmptyEllipsis:
|
||||
value: int
|
||||
... # Y013 Non-empty class body must not contain `...`
|
||||
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
|
||||
def function():
|
||||
pass
|
||||
|
||||
|
||||
pass
|
||||
59
crates/ruff/resources/test/fixtures/flake8_pyi/PYI012.pyi
vendored
Normal file
59
crates/ruff/resources/test/fixtures/flake8_pyi/PYI012.pyi
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# Violations of PYI012
|
||||
|
||||
class OneAttributeClass:
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
class OneAttributeClassRev:
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
value: int
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains pass.
|
||||
"""
|
||||
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
value: int
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
pass # PYI012 Class body must not contain `pass`
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
# Not violations (of PYI012)
|
||||
|
||||
class EmptyClass:
|
||||
pass # Y009 Empty body should contain `...`, not `pass`
|
||||
|
||||
class EmptyOneLine:
|
||||
pass # Y009 Empty body should contain `...`, not `pass`
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
class EmptyEllipsis: ...
|
||||
|
||||
class NonEmptyEllipsis:
|
||||
value: int
|
||||
... # Y013 Non-empty class body must not contain `...`
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
def function():
|
||||
pass
|
||||
|
||||
pass
|
||||
@@ -39,6 +39,58 @@ def f15(
|
||||
...
|
||||
|
||||
|
||||
def f151(x={1: 2}) -> None: # Ok
|
||||
...
|
||||
|
||||
|
||||
def f152(
|
||||
x={ # OK
|
||||
1: 2,
|
||||
**{3: 4},
|
||||
}
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f153(
|
||||
x=[ # OK
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f154(
|
||||
x=( # OK
|
||||
"foo",
|
||||
("bar", "baz"),
|
||||
)
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f141(
|
||||
x=[*range(10)], # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f142(
|
||||
x=list(range(10)), # OK
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
|
||||
... # OK
|
||||
|
||||
|
||||
@@ -4,26 +4,60 @@ def f12(
|
||||
) -> None: ...
|
||||
def f11(*, x="x") -> None: ... # OK
|
||||
def f13(
|
||||
x=[ # Error PYI014
|
||||
x=[ # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
]
|
||||
) -> None: ...
|
||||
def f14(
|
||||
x=( # Error PYI014
|
||||
x=( # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
)
|
||||
) -> None: ...
|
||||
def f15(
|
||||
x={ # Error PYI014
|
||||
x={ # OK
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
}
|
||||
) -> None: ...
|
||||
def f151(x={1: 2}) -> None: ...
|
||||
def f152(
|
||||
x={ # Error PYI014
|
||||
1: 2,
|
||||
**{3: 4},
|
||||
}
|
||||
) -> None: ...
|
||||
def f153(
|
||||
x=[ # Error PYI014
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
]
|
||||
) -> None: ...
|
||||
def f154(
|
||||
x=( # Error PYI014
|
||||
"foo",
|
||||
("bar", "baz"),
|
||||
)
|
||||
) -> None: ...
|
||||
def f141(
|
||||
x=[*range(10)], # Error PYI014
|
||||
) -> None: ...
|
||||
def f142(
|
||||
x=list(range(10)), # Error PYI014
|
||||
) -> None: ...
|
||||
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
|
||||
def f17(
|
||||
x="foo" + "bar", # Error PYI014
|
||||
@@ -44,5 +78,5 @@ def f22(
|
||||
x=-42.5j + 4.3j, # Error PYI014
|
||||
) -> None: ...
|
||||
def f23(
|
||||
x=True, # OK
|
||||
x=True, # OK
|
||||
) -> None: ...
|
||||
|
||||
48
crates/ruff/resources/test/fixtures/flake8_pyi/PYI015.py
vendored
Normal file
48
crates/ruff/resources/test/fixtures/flake8_pyi/PYI015.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import builtins
|
||||
import typing
|
||||
from typing import TypeAlias, Final
|
||||
|
||||
field1: int
|
||||
field2: int = ...
|
||||
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||
field4: int = 0
|
||||
field41: int = 0xFFFFFFFF
|
||||
field42: int = 1234567890
|
||||
field43: int = -0xFFFFFFFF
|
||||
field44: int = -1234567890
|
||||
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||
field6 = 0 # Y052 Need type annotation for "field6"
|
||||
field7 = b"" # Y052 Need type annotation for "field7"
|
||||
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||
field72: str = "foo"
|
||||
field8 = False # Y052 Need type annotation for "field8"
|
||||
field81 = -1 # Y052 Need type annotation for "field81"
|
||||
field82: float = -98.43
|
||||
field83 = -42j # Y052 Need type annotation for "field83"
|
||||
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||
Field95: TypeAlias = None
|
||||
Field96: TypeAlias = int | None
|
||||
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||
field191: list[int] = [1, 2, 3]
|
||||
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||
field201: tuple[int, ...] = (1, 2, 3)
|
||||
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||
field211: set[int] = {1, 2, 3}
|
||||
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||
field213: dict[str, str] = {"foo": "bar"}
|
||||
field22: Final = {"foo": 5}
|
||||
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||
55
crates/ruff/resources/test/fixtures/flake8_pyi/PYI015.pyi
vendored
Normal file
55
crates/ruff/resources/test/fixtures/flake8_pyi/PYI015.pyi
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import builtins
|
||||
import typing
|
||||
from typing import TypeAlias, Final, NewType, TypeVar, TypeVarTuple, ParamSpec
|
||||
|
||||
# We shouldn't emit Y015 for simple default values
|
||||
field1: int
|
||||
field2: int = ...
|
||||
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||
field4: int = 0
|
||||
field41: int = 0xFFFFFFFF
|
||||
field42: int = 1234567890
|
||||
field43: int = -0xFFFFFFFF
|
||||
field44: int = -1234567890
|
||||
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||
field6 = 0 # Y052 Need type annotation for "field6"
|
||||
field7 = b"" # Y052 Need type annotation for "field7"
|
||||
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||
field72: str = "foo"
|
||||
field8 = False # Y052 Need type annotation for "field8"
|
||||
field81 = -1 # Y052 Need type annotation for "field81"
|
||||
field82: float = -98.43
|
||||
field83 = -42j # Y052 Need type annotation for "field83"
|
||||
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||
Field95: TypeAlias = None
|
||||
Field96: TypeAlias = int | None
|
||||
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||
Field98 = NewType('MyInt', int)
|
||||
Field99 = TypeVar('Field99')
|
||||
Field100 = TypeVarTuple('Field100')
|
||||
Field101 = ParamSpec('Field101')
|
||||
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||
field191: list[int] = [1, 2, 3]
|
||||
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||
field201: tuple[int, ...] = (1, 2, 3)
|
||||
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||
field211: set[int] = {1, 2, 3}
|
||||
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||
field213: dict[str, str] = {"foo": "bar"}
|
||||
field22: Final = {"foo": 5}
|
||||
|
||||
# We *should* emit Y015 for more complex default values
|
||||
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||
@@ -2,3 +2,13 @@ def x(y):
|
||||
if not y:
|
||||
return
|
||||
return None # error
|
||||
|
||||
|
||||
class BaseCache:
|
||||
def get(self, key: str) -> str | None:
|
||||
print(f"{key} not found")
|
||||
return None
|
||||
|
||||
def get(self, key: str) -> None:
|
||||
print(f"{key} not found")
|
||||
return None
|
||||
|
||||
@@ -16,15 +16,26 @@ if isinstance(a, int) or isinstance(b, bool) or isinstance(a, float): # SIM101
|
||||
if (isinstance(a, int) or isinstance(a, float)) and isinstance(b, bool): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a.b, int) or isinstance(a.b, float): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a(), int) or isinstance(a(), float): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a, int) and isinstance(b, bool) or isinstance(a, float):
|
||||
pass
|
||||
|
||||
if isinstance(a, bool) or isinstance(b, str):
|
||||
pass
|
||||
|
||||
if isinstance(a, int) or isinstance(a.b, float):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
def isinstance(a, b):
|
||||
return False
|
||||
|
||||
if isinstance(a, int) or isinstance(a, float):
|
||||
pass
|
||||
|
||||
@@ -46,10 +46,10 @@ if a:
|
||||
if b:
|
||||
c
|
||||
|
||||
while True:
|
||||
while x > 0:
|
||||
# SIM102
|
||||
if True:
|
||||
if True:
|
||||
if y > 0:
|
||||
if z > 0:
|
||||
"""this
|
||||
is valid"""
|
||||
|
||||
@@ -64,8 +64,8 @@ is valid"""
|
||||
|
||||
|
||||
# SIM102
|
||||
if True:
|
||||
if True:
|
||||
if x > 0:
|
||||
if y > 0:
|
||||
"""this
|
||||
is valid"""
|
||||
|
||||
@@ -78,7 +78,7 @@ is valid"""
|
||||
("so is"
|
||||
"this for some reason")
|
||||
|
||||
while True:
|
||||
while x > 0:
|
||||
# SIM102
|
||||
if node.module:
|
||||
if node.module == "multiprocessing" or node.module.startswith(
|
||||
@@ -129,3 +129,15 @@ if a:
|
||||
print("baz")
|
||||
else:
|
||||
print("bar")
|
||||
|
||||
|
||||
# OK
|
||||
if False:
|
||||
if a:
|
||||
pass
|
||||
|
||||
|
||||
# OK
|
||||
if True:
|
||||
if a:
|
||||
pass
|
||||
|
||||
@@ -9,6 +9,17 @@ os.environ.get('foo', 'bar')
|
||||
|
||||
os.getenv('foo')
|
||||
|
||||
env = os.environ.get('foo')
|
||||
|
||||
env = os.environ['foo']
|
||||
|
||||
if env := os.environ.get('foo'):
|
||||
pass
|
||||
|
||||
if env := os.environ['foo']:
|
||||
pass
|
||||
|
||||
|
||||
# Good
|
||||
os.environ['FOO']
|
||||
|
||||
@@ -17,3 +28,13 @@ os.environ.get('FOO')
|
||||
os.environ.get('FOO', 'bar')
|
||||
|
||||
os.getenv('FOO')
|
||||
|
||||
env = os.getenv('FOO')
|
||||
|
||||
if env := os.getenv('FOO'):
|
||||
pass
|
||||
|
||||
env = os.environ['FOO']
|
||||
|
||||
if env := os.environ['FOO']:
|
||||
pass
|
||||
|
||||
@@ -20,3 +20,5 @@ for key in list(obj.keys()):
|
||||
{k: k for k in obj.keys()} # SIM118
|
||||
|
||||
(k for k in obj.keys()) # SIM118
|
||||
|
||||
key in (obj or {}).keys() # SIM118
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
if a or True: # SIM223
|
||||
if a or True: # SIM222
|
||||
pass
|
||||
|
||||
if (a or b) or True: # SIM223
|
||||
if (a or b) or True: # SIM222
|
||||
pass
|
||||
|
||||
if a or (b or True): # SIM223
|
||||
if a or (b or True): # SIM222
|
||||
pass
|
||||
|
||||
if a and True: # OK
|
||||
@@ -16,3 +16,29 @@ if True: # OK
|
||||
|
||||
def validate(self, value):
|
||||
return json.loads(value) or True # OK
|
||||
|
||||
|
||||
if a or f() or b or g() or True: # OK
|
||||
pass
|
||||
|
||||
if a or f() or True or g() or b: # SIM222
|
||||
pass
|
||||
|
||||
if True or f() or a or g() or b: # SIM222
|
||||
pass
|
||||
|
||||
if a or True or f() or b or g(): # SIM222
|
||||
pass
|
||||
|
||||
|
||||
if a and f() and b and g() and False: # OK
|
||||
pass
|
||||
|
||||
if a and f() and False and g() and b: # OK
|
||||
pass
|
||||
|
||||
if False and f() and a and g() and b: # OK
|
||||
pass
|
||||
|
||||
if a and False and f() and b and g(): # OK
|
||||
pass
|
||||
|
||||
@@ -12,3 +12,28 @@ if a or False:
|
||||
|
||||
if False:
|
||||
pass
|
||||
|
||||
if a and f() and b and g() and False: # OK
|
||||
pass
|
||||
|
||||
if a and f() and False and g() and b: # SIM223
|
||||
pass
|
||||
|
||||
if False and f() and a and g() and b: # SIM223
|
||||
pass
|
||||
|
||||
if a and False and f() and b and g(): # SIM223
|
||||
pass
|
||||
|
||||
|
||||
if a or f() or b or g() or True: # OK
|
||||
pass
|
||||
|
||||
if a or f() or True or g() or b: # OK
|
||||
pass
|
||||
|
||||
if True or f() or a or g() or b: # OK
|
||||
pass
|
||||
|
||||
if a or True or f() or b or g(): # OK
|
||||
pass
|
||||
|
||||
27
crates/ruff/resources/test/fixtures/flake8_simplify/SIM910.py
vendored
Normal file
27
crates/ruff/resources/test/fixtures/flake8_simplify/SIM910.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# SIM910
|
||||
{}.get(key, None)
|
||||
|
||||
# SIM910
|
||||
{}.get("key", None)
|
||||
|
||||
# OK
|
||||
{}.get(key)
|
||||
|
||||
# OK
|
||||
{}.get("key")
|
||||
|
||||
# OK
|
||||
{}.get(key, False)
|
||||
|
||||
# OK
|
||||
{}.get("key", False)
|
||||
|
||||
# SIM910
|
||||
if a := {}.get(key, None):
|
||||
pass
|
||||
|
||||
# SIM910
|
||||
a = {}.get(key, None)
|
||||
|
||||
# SIM910
|
||||
({}).get(key, None)
|
||||
@@ -7,3 +7,4 @@ from ..protocol import commands, definitions, responses
|
||||
from ..server import example
|
||||
from .. import server
|
||||
from . import logger, models
|
||||
from ..protocol.UpperCaseModule import some_function
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
def some_function():
|
||||
pass
|
||||
@@ -2,7 +2,9 @@ from a import a1 # import_from
|
||||
from c import * # import_from_star
|
||||
import a # import
|
||||
import c.d
|
||||
from z import z1
|
||||
import b as b1 # import_as
|
||||
import z
|
||||
|
||||
from ..parent import *
|
||||
from .my import fn
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import sys
|
||||
import baz
|
||||
from foo import bar, baz
|
||||
from foo.bar import blah, blub
|
||||
from foo.bar.baz import something
|
||||
import foo
|
||||
import foo.bar
|
||||
import foo.bar.baz
|
||||
@@ -18,3 +18,7 @@ if True:
|
||||
columns=["a"],
|
||||
axis=1,
|
||||
)
|
||||
|
||||
x.drop(["a"], axis=1, **kwargs, inplace=True)
|
||||
x.drop(["a"], axis=1, inplace=True, **kwargs)
|
||||
f(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/invalid_name/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/invalid_name/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/invalid_name/import.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/invalid_name/import.py
vendored
Normal file
@@ -13,3 +13,8 @@ result = {
|
||||
'key1': 'value',
|
||||
'key2': 'value',
|
||||
}
|
||||
|
||||
def foo() -> None:
|
||||
#: E231
|
||||
if (1,2):
|
||||
pass
|
||||
|
||||
@@ -56,7 +56,29 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
|
||||
# OK
|
||||
# https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
|
||||
|
||||
# Not OK
|
||||
# OK
|
||||
_ = """
|
||||
Source: https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
"""
|
||||
|
||||
# OK
|
||||
_ = """
|
||||
[this-is-ok](https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533)
|
||||
[this is ok](https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533)
|
||||
"""
|
||||
|
||||
|
||||
# OK
|
||||
class Foo:
|
||||
"""
|
||||
@see https://looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
|
||||
|
||||
:param dynamodb_scan_kwargs: kwargs pass to <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.scan>
|
||||
"""
|
||||
|
||||
|
||||
# Error
|
||||
class Bar:
|
||||
"""
|
||||
This is a long sentence that ends with a shortened URL and, therefore, could easily be broken across multiple lines ([source](https://ruff.rs))
|
||||
"""
|
||||
|
||||
15
crates/ruff/resources/test/fixtures/pydocstyle/D403.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/pydocstyle/D403.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
def bad_function():
|
||||
"""this docstring is not capitalized"""
|
||||
|
||||
def good_function():
|
||||
"""This docstring is capitalized."""
|
||||
|
||||
def other_function():
|
||||
"""
|
||||
This docstring is capitalized."""
|
||||
|
||||
def another_function():
|
||||
""" This docstring is capitalized."""
|
||||
|
||||
def utf8_function():
|
||||
"""éste docstring is capitalized."""
|
||||
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/_no_pkg_priv.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/_no_pkg_priv.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/D100_pub.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/D100_pub.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/_priv/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/_priv/__init__.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/_priv/no_D100_priv.py
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pydocstyle/_unrelated/pkg/_priv/no_D100_priv.py
vendored
Normal file
@@ -1,10 +1,19 @@
|
||||
"""Test: imports within `ModuleNotFoundError` handlers."""
|
||||
"""Test: imports within `ModuleNotFoundError` and `ImportError` handlers."""
|
||||
|
||||
|
||||
def check_orjson():
|
||||
def module_not_found_error():
|
||||
try:
|
||||
import orjson
|
||||
|
||||
return True
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
|
||||
|
||||
def import_error():
|
||||
try:
|
||||
import orjson
|
||||
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
7
crates/ruff/resources/test/fixtures/pyflakes/F401_11.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/pyflakes/F401_11.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Test: parsing of nested string annotations."""
|
||||
|
||||
from typing import List
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
|
||||
x: """List['Path']""" = []
|
||||
@@ -8,3 +8,6 @@ def f() -> "A":
|
||||
|
||||
def g() -> "///":
|
||||
pass
|
||||
|
||||
|
||||
X: """List[int]"""'☃' = []
|
||||
|
||||
8
crates/ruff/resources/test/fixtures/pyflakes/F821_13.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/pyflakes/F821_13.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Test case: ForwardRef."""
|
||||
|
||||
from typing import ForwardRef, TypeVar
|
||||
|
||||
X = ForwardRef("List[int]")
|
||||
Y: ForwardRef("List[int]")
|
||||
|
||||
Z = TypeVar("X", "List[int]", "int")
|
||||
7
crates/ruff/resources/test/fixtures/pyflakes/F821_14.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/pyflakes/F821_14.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Test case: f-strings in type annotations."""
|
||||
|
||||
from typing import List
|
||||
|
||||
x = 1
|
||||
|
||||
x: List[f"i{x}nt"] = []
|
||||
9
crates/ruff/resources/test/fixtures/pyflakes/F821_15.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/pyflakes/F821_15.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Test case: f-strings in future type annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
x = 1
|
||||
|
||||
x: List[f"i{x}nt"] = []
|
||||
@@ -9,4 +9,8 @@ def main():
|
||||
quit(1)
|
||||
|
||||
|
||||
sys.exit(2)
|
||||
def main():
|
||||
sys = 1
|
||||
|
||||
exit(1)
|
||||
quit(1)
|
||||
|
||||
@@ -7,3 +7,10 @@ quit(0)
|
||||
def main():
|
||||
exit(1)
|
||||
quit(1)
|
||||
|
||||
|
||||
def main():
|
||||
exit = 1
|
||||
|
||||
exit(1)
|
||||
quit(1)
|
||||
|
||||
@@ -48,3 +48,13 @@ def print_python_version():
|
||||
"""This function returns None."""
|
||||
print(sys.version)
|
||||
return None # [useless-return]
|
||||
|
||||
|
||||
class BaseCache:
|
||||
def get(self, key: str) -> str | None:
|
||||
print(f"{key} not found")
|
||||
return None
|
||||
|
||||
def get(self, key: str) -> None:
|
||||
print(f"{key} not found")
|
||||
return None
|
||||
|
||||
@@ -30,8 +30,29 @@ def f(x: "List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
list = "abc"
|
||||
|
||||
|
||||
def f(x: List[str]) -> None:
|
||||
def f(x: r"List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "List[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: """List[str]""") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "Li" "st[str]") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "List['List[str]']") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "List['Li' 'st[str]']") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "Li" "st['List[str]']") -> None:
|
||||
...
|
||||
|
||||
@@ -29,10 +29,13 @@ string = "hello there"
|
||||
string.encode("utf-8")
|
||||
|
||||
bar = "bar"
|
||||
f"foo{bar}".encode("utf-8") # f"foo{bar}".encode()
|
||||
f"foo{bar}".encode("utf-8")
|
||||
encoding = "latin"
|
||||
"foo".encode(encoding)
|
||||
f"foo{bar}".encode(encoding)
|
||||
f"{a=} {b=}".encode(
|
||||
"utf-8",
|
||||
)
|
||||
|
||||
# `encode` with custom args and kwargs should not be processed.
|
||||
"foo".encode("utf-8", errors="replace")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import functools
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
@@ -7,11 +6,6 @@ def fixme():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def fixme():
|
||||
pass
|
||||
|
||||
|
||||
@other_decorator
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def fixme():
|
||||
@@ -29,31 +23,16 @@ def ok():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=64)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
def user_func():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(user_func)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(user_func, maxsize=None)
|
||||
@functools.lru_cache(user_func)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
51
crates/ruff/resources/test/fixtures/pyupgrade/UP033_1.py
vendored
Normal file
51
crates/ruff/resources/test/fixtures/pyupgrade/UP033_1.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def fixme():
|
||||
pass
|
||||
|
||||
|
||||
@other_decorator
|
||||
@lru_cache(maxsize=None)
|
||||
def fixme():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
@other_decorator
|
||||
def fixme():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
def user_func():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(user_func)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(user_func, maxsize=None)
|
||||
def ok():
|
||||
pass
|
||||
|
||||
|
||||
def lru_cache(maxsize=None):
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def ok():
|
||||
pass
|
||||
@@ -1,5 +1,6 @@
|
||||
input = [1, 2, 3]
|
||||
otherInput = [2, 3, 4]
|
||||
foo = [1, 2, 3, 4]
|
||||
|
||||
# OK
|
||||
zip(input, otherInput) # different inputs
|
||||
@@ -8,6 +9,8 @@ zip(input, input[2:]) # not successive
|
||||
zip(input[:-1], input[2:]) # not successive
|
||||
list(zip(input, otherInput)) # nested call
|
||||
zip(input, input[1::2]) # not successive
|
||||
zip(foo[:-1], foo[1:], foo, strict=False) # more than 2 inputs
|
||||
zip(foo[:-1], foo[1:], foo, strict=True) # more than 2 inputs
|
||||
|
||||
# Errors
|
||||
zip(input, input[1:])
|
||||
@@ -17,3 +20,6 @@ zip(input[1:], input[2:])
|
||||
zip(input[1:-1], input[2:])
|
||||
list(zip(input, input[1:]))
|
||||
list(zip(input[:-1], input[1:]))
|
||||
zip(foo[:-1], foo[1:], strict=True)
|
||||
zip(foo[:-1], foo[1:], strict=False)
|
||||
zip(foo[:-1], foo[1:], strict=bool(foo))
|
||||
|
||||
@@ -40,8 +40,9 @@ def noreturn():
|
||||
logger.exception("process failed")
|
||||
|
||||
|
||||
def still_good():
|
||||
def good_return_with_side_effects():
|
||||
try:
|
||||
pass
|
||||
return process()
|
||||
except MyException:
|
||||
logger.exception("process failed")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use libcst_native::{
|
||||
@@ -6,14 +7,17 @@ use libcst_native::{
|
||||
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::helpers::to_absolute;
|
||||
use ruff_python_ast::imports::{AnyImport, Import};
|
||||
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_semantic::context::Context;
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::importer::Importer;
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
@@ -178,7 +182,7 @@ pub fn delete_stmt(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Fix> {
|
||||
) -> Result<Edit> {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
@@ -186,7 +190,7 @@ pub fn delete_stmt(
|
||||
{
|
||||
// If removing this node would lead to an invalid syntax tree, replace
|
||||
// it with a `pass`.
|
||||
Ok(Fix::replacement(
|
||||
Ok(Edit::replacement(
|
||||
"pass".to_string(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
@@ -194,22 +198,22 @@ pub fn delete_stmt(
|
||||
} else {
|
||||
Ok(if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
let next = next_stmt_break(semicolon, locator);
|
||||
Fix::deletion(stmt.location, next)
|
||||
Edit::deletion(stmt.location, next)
|
||||
} else if helpers::match_leading_content(stmt, locator) {
|
||||
Fix::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
Edit::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
} else if helpers::preceded_by_continuation(stmt, indexer) {
|
||||
if is_end_of_file(stmt, locator) && stmt.location.column() == 0 {
|
||||
// Special-case: a file can't end in a continuation.
|
||||
Fix::replacement(
|
||||
Edit::replacement(
|
||||
stylist.line_ending().to_string(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
)
|
||||
} else {
|
||||
Fix::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
Edit::deletion(stmt.location, stmt.end_location.unwrap())
|
||||
}
|
||||
} else {
|
||||
Fix::deletion(
|
||||
Edit::deletion(
|
||||
Location::new(stmt.location.row(), 0),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
)
|
||||
@@ -226,7 +230,7 @@ pub fn remove_unused_imports<'a>(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Fix> {
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(stmt);
|
||||
let mut tree = match_module(module_text)?;
|
||||
|
||||
@@ -327,13 +331,13 @@ pub fn remove_unused_imports<'a>(
|
||||
delete_stmt(stmt, parent, deleted, locator, indexer, stylist)
|
||||
} else {
|
||||
let mut state = CodegenState {
|
||||
default_newline: stylist.line_ending(),
|
||||
default_newline: &stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
Ok(Edit::replacement(
|
||||
state.to_string(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
@@ -349,15 +353,15 @@ pub fn remove_unused_imports<'a>(
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub fn remove_argument(
|
||||
locator: &Locator,
|
||||
stmt_at: Location,
|
||||
call_at: Location,
|
||||
expr_at: Location,
|
||||
expr_end: Location,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
remove_parentheses: bool,
|
||||
) -> Result<Fix> {
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
let contents = locator.skip(stmt_at);
|
||||
let contents = locator.skip(call_at);
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
@@ -370,7 +374,7 @@ pub fn remove_argument(
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::lex_located(contents, Mode::Module, stmt_at).flatten() {
|
||||
for (start, tok, end) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
@@ -402,7 +406,7 @@ pub fn remove_argument(
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (start, tok, end) in lexer::lex_located(contents, Mode::Module, stmt_at).flatten() {
|
||||
for (start, tok, end) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::NonLogicalNewline) {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
@@ -425,7 +429,7 @@ pub fn remove_argument(
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (start, tok, _) in lexer::lex_located(contents, Mode::Module, stmt_at).flatten() {
|
||||
for (start, tok, _) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
if start == expr_at {
|
||||
fix_end = Some(expr_end);
|
||||
break;
|
||||
@@ -437,13 +441,89 @@ pub fn remove_argument(
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Fix::deletion(start, end)),
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make the
|
||||
/// symbol available in the current scope along with the bound name of the symbol.
|
||||
///
|
||||
/// For example, assuming `module` is `"functools"` and `member` is `"lru_cache"`, this function
|
||||
/// could return an [`Edit`] to add `import functools` to the top of the file, alongside with the
|
||||
/// name on which the `lru_cache` symbol would be made available (`"functools.lru_cache"`).
|
||||
///
|
||||
/// Attempts to reuse existing imports when possible.
|
||||
pub fn get_or_import_symbol(
|
||||
module: &str,
|
||||
member: &str,
|
||||
context: &Context,
|
||||
importer: &Importer,
|
||||
locator: &Locator,
|
||||
) -> Result<(Edit, String)> {
|
||||
if let Some((source, binding)) = context.resolve_qualified_import_name(module, member) {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
//
|
||||
// We also add a no-nop edit to force conflicts with any other fixes that might try to
|
||||
// remove the import. Consider:
|
||||
//
|
||||
// ```py
|
||||
// import sys
|
||||
//
|
||||
// quit()
|
||||
// ```
|
||||
//
|
||||
// Assume you omit this no-op edit. If you run Ruff with `unused-imports` and
|
||||
// `sys-exit-alias` over this snippet, it will generate two fixes: (1) remove the unused
|
||||
// `sys` import; and (2) replace `quit()` with `sys.exit()`, under the assumption that `sys`
|
||||
// is already imported and available.
|
||||
//
|
||||
// By adding this no-op edit, we force the `unused-imports` fix to conflict with the
|
||||
// `sys-exit-alias` fix, and thus will avoid applying both fixes in the same pass.
|
||||
let import_edit = Edit::replacement(
|
||||
locator.slice(source).to_string(),
|
||||
source.location,
|
||||
source.end_location.unwrap(),
|
||||
);
|
||||
Ok((import_edit, binding))
|
||||
} else {
|
||||
if let Some(stmt) = importer.get_import_from(module) {
|
||||
// Case 1: `from functools import lru_cache` is in scope, and we're trying to reference
|
||||
// `functools.cache`; thus, we add `cache` to the import, and return `"cache"` as the
|
||||
// bound name.
|
||||
if context
|
||||
.find_binding(member)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = importer.add_member(stmt, member)?;
|
||||
Ok((import_edit, member.to_string()))
|
||||
} else {
|
||||
bail!(
|
||||
"Unable to insert `{}` into scope due to name conflict",
|
||||
member
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Case 2: No `functools` import is in scope; thus, we add `import functools`, and
|
||||
// return `"functools.cache"` as the bound name.
|
||||
if context
|
||||
.find_binding(module)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = importer.add_import(&AnyImport::Import(Import::module(module)));
|
||||
Ok((import_edit, format!("{module}.{member}")))
|
||||
} else {
|
||||
bail!(
|
||||
"Unable to insert `{}` into scope due to name conflict",
|
||||
module
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
@@ -452,7 +532,7 @@ mod tests {
|
||||
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::helpers::{next_stmt_break, trailing_semicolon};
|
||||
use crate::autofix::actions::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
@@ -4,18 +4,18 @@ use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub mod helpers;
|
||||
pub mod actions;
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<(String, FixTable)> {
|
||||
if diagnostics.iter().all(|check| check.fix.is_none()) {
|
||||
if diagnostics.iter().all(|check| check.fix.is_empty()) {
|
||||
None
|
||||
} else {
|
||||
Some(apply_fixes(diagnostics.iter(), locator))
|
||||
@@ -29,41 +29,48 @@ fn apply_fixes<'a>(
|
||||
) -> (String, FixTable) {
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Option<Location> = None;
|
||||
let mut applied: BTreeSet<&Fix> = BTreeSet::default();
|
||||
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
|
||||
let mut fixed = FxHashMap::default();
|
||||
|
||||
for (rule, fix) in diagnostics
|
||||
.filter_map(|diagnostic| {
|
||||
diagnostic
|
||||
.fix
|
||||
.as_ref()
|
||||
.map(|fix| (diagnostic.kind.rule(), fix))
|
||||
if diagnostic.fix.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((diagnostic.kind.rule(), &diagnostic.fix))
|
||||
}
|
||||
})
|
||||
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
|
||||
{
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if applied.contains(&fix) {
|
||||
if fix.edits().iter().all(|edit| applied.contains(edit)) {
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied,
|
||||
// skip it.
|
||||
if last_pos.map_or(false, |last_pos| last_pos >= fix.location) {
|
||||
if last_pos.map_or(false, |last_pos| {
|
||||
fix.location()
|
||||
.map_or(false, |fix_location| last_pos >= fix_location)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(Range::new(last_pos.unwrap_or_default(), fix.location));
|
||||
output.push_str(slice);
|
||||
for edit in fix.edits() {
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(Range::new(last_pos.unwrap_or_default(), edit.location));
|
||||
output.push_str(slice);
|
||||
|
||||
// Add the patch itself.
|
||||
output.push_str(&fix.content);
|
||||
// Add the patch itself.
|
||||
output.push_str(&edit.content);
|
||||
|
||||
// Track that the edit was applied.
|
||||
last_pos = Some(edit.end_location);
|
||||
applied.insert(edit);
|
||||
}
|
||||
|
||||
// Track that the fix was applied.
|
||||
last_pos = Some(fix.end_location);
|
||||
applied.insert(fix);
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
}
|
||||
|
||||
@@ -74,28 +81,10 @@ fn apply_fixes<'a>(
|
||||
(output, fixed)
|
||||
}
|
||||
|
||||
/// Apply a single fix.
|
||||
pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String {
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(Range::new(Location::new(1, 0), fix.location));
|
||||
output.push_str(slice);
|
||||
|
||||
// Add the patch itself.
|
||||
output.push_str(&fix.content);
|
||||
|
||||
// Add the remaining content.
|
||||
let slice = locator.skip(fix.end_location);
|
||||
output.push_str(slice);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Compare two fixes.
|
||||
fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering {
|
||||
fix1.location
|
||||
.cmp(&fix2.location)
|
||||
fix1.location()
|
||||
.cmp(&fix2.location())
|
||||
.then_with(|| match (&rule1, &rule2) {
|
||||
// Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes.
|
||||
(Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
|
||||
@@ -109,21 +98,20 @@ mod tests {
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::{apply_fix, apply_fixes};
|
||||
use crate::autofix::apply_fixes;
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
|
||||
fn create_diagnostics(fixes: impl IntoIterator<Item = Fix>) -> Vec<Diagnostic> {
|
||||
fixes
|
||||
.into_iter()
|
||||
.map(|fix| Diagnostic {
|
||||
fn create_diagnostics(edit: impl IntoIterator<Item = Edit>) -> Vec<Diagnostic> {
|
||||
edit.into_iter()
|
||||
.map(|edit| Diagnostic {
|
||||
// The choice of rule here is arbitrary.
|
||||
kind: MissingNewlineAtEndOfFile.into(),
|
||||
location: fix.location,
|
||||
end_location: fix.end_location,
|
||||
fix: Some(fix),
|
||||
location: edit.location,
|
||||
end_location: edit.end_location,
|
||||
fix: edit.into(),
|
||||
parent: None,
|
||||
})
|
||||
.collect()
|
||||
@@ -147,7 +135,7 @@ class A(object):
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let diagnostics = create_diagnostics([Fix {
|
||||
let diagnostics = create_diagnostics([Edit {
|
||||
content: "Bar".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 14),
|
||||
@@ -173,7 +161,7 @@ class A(object):
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let diagnostics = create_diagnostics([Fix {
|
||||
let diagnostics = create_diagnostics([Edit {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
@@ -200,12 +188,12 @@ class A(object, object, object):
|
||||
.trim(),
|
||||
);
|
||||
let diagnostics = create_diagnostics([
|
||||
Fix {
|
||||
Edit {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
},
|
||||
Fix {
|
||||
Edit {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 22),
|
||||
end_location: Location::new(1, 30),
|
||||
@@ -234,12 +222,12 @@ class A(object):
|
||||
.trim(),
|
||||
);
|
||||
let diagnostics = create_diagnostics([
|
||||
Fix {
|
||||
Edit {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
Fix {
|
||||
Edit {
|
||||
content: "ignored".to_string(),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 11),
|
||||
@@ -256,31 +244,4 @@ class A:
|
||||
);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_single_fix() {
|
||||
let locator = Locator::new(
|
||||
r#"
|
||||
class A(object):
|
||||
...
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let contents = apply_fix(
|
||||
&Fix {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
&locator,
|
||||
);
|
||||
assert_eq!(
|
||||
contents,
|
||||
r#"
|
||||
class A:
|
||||
...
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use ruff_python_ast::scope::ScopeStack;
|
||||
use ruff_python_semantic::scope::ScopeStack;
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
|
||||
use ruff_python_ast::types::Range;
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_ast::visibility::{Visibility, VisibleScope};
|
||||
use ruff_python_semantic::analyze::visibility::{Visibility, VisibleScope};
|
||||
|
||||
use crate::checkers::ast::AnnotationContext;
|
||||
use crate::docstrings::definition::Definition;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
||||
//! Lint rules based on import analysis.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::Suite;
|
||||
use rustpython_parser::ast::{StmtKind, Suite};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
use ruff_python_ast::imports::{ImportMap, ModuleImport};
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
|
||||
@@ -14,6 +16,66 @@ use crate::rules::isort;
|
||||
use crate::rules::isort::track::{Block, ImportTracker};
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) -> Option<ImportMap> {
|
||||
let Some(package) = package else {
|
||||
return None;
|
||||
};
|
||||
let Some(module_path) = to_module_path(package, path) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let num_imports = blocks.iter().map(|block| block.imports.len()).sum();
|
||||
let mut module_imports = Vec::with_capacity(num_imports);
|
||||
for stmt in blocks.iter().flat_map(|block| &block.imports) {
|
||||
match &stmt.node {
|
||||
StmtKind::Import { names } => {
|
||||
module_imports.extend(names.iter().map(|name| {
|
||||
ModuleImport::new(
|
||||
name.node.name.clone(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
)
|
||||
}));
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
let level = level.unwrap_or(0);
|
||||
let module = if let Some(module) = module {
|
||||
if level == 0 {
|
||||
Cow::Borrowed(module)
|
||||
} else {
|
||||
if module_path.len() <= level {
|
||||
continue;
|
||||
}
|
||||
let prefix = module_path[..module_path.len() - level].join(".");
|
||||
Cow::Owned(format!("{prefix}.{module}"))
|
||||
}
|
||||
} else {
|
||||
if module_path.len() <= level {
|
||||
continue;
|
||||
}
|
||||
Cow::Owned(module_path[..module_path.len() - level].join("."))
|
||||
};
|
||||
module_imports.extend(names.iter().map(|name| {
|
||||
ModuleImport::new(
|
||||
format!("{}.{}", module, name.node.name),
|
||||
name.location,
|
||||
name.end_location.unwrap(),
|
||||
)
|
||||
}));
|
||||
}
|
||||
_ => panic!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut import_map = ImportMap::default();
|
||||
import_map.insert(module_path.join("."), module_imports);
|
||||
Some(import_map)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_imports(
|
||||
python_ast: &Suite,
|
||||
@@ -25,13 +87,11 @@ pub fn check_imports(
|
||||
autofix: flags::Autofix,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
// Extract all imports from the AST.
|
||||
let tracker = {
|
||||
let mut tracker = ImportTracker::new(locator, directives, path);
|
||||
for stmt in python_ast {
|
||||
tracker.visit_stmt(stmt);
|
||||
}
|
||||
tracker.visit_body(python_ast);
|
||||
tracker
|
||||
};
|
||||
let blocks: Vec<&Block> = tracker.iter().collect();
|
||||
@@ -54,5 +114,9 @@ pub fn check_imports(
|
||||
&blocks, python_ast, locator, stylist, settings, autofix,
|
||||
));
|
||||
}
|
||||
diagnostics
|
||||
|
||||
// Extract import map.
|
||||
let imports = extract_import_map(path, package, &blocks);
|
||||
|
||||
(diagnostics, imports)
|
||||
}
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables)]
|
||||
|
||||
use bisection::bisect_left;
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
|
||||
use crate::rules::pycodestyle::rules::{
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
||||
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
|
||||
whitespace_around_named_parameter_equals, whitespace_before_comment,
|
||||
whitespace_before_parameters,
|
||||
whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||
};
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
||||
fn expand_indent(mut line: &str) -> usize {
|
||||
while line.ends_with("\n\r") {
|
||||
line = &line[..line.len() - 2];
|
||||
}
|
||||
if !line.contains('\t') {
|
||||
return line.len() - line.trim_start().len();
|
||||
}
|
||||
fn expand_indent(line: &str) -> usize {
|
||||
let line = line.trim_end_matches(['\n', '\r']);
|
||||
|
||||
let mut indent = 0;
|
||||
for c in line.chars() {
|
||||
if c == '\t' {
|
||||
indent = (indent / 8) * 8 + 8;
|
||||
} else if c == ' ' {
|
||||
indent += 1;
|
||||
} else {
|
||||
break;
|
||||
for c in line.bytes() {
|
||||
match c {
|
||||
b'\t' => indent = (indent / 8) * 8 + 8,
|
||||
b' ' => indent += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
indent
|
||||
}
|
||||
|
||||
@@ -49,151 +39,140 @@ pub fn check_logical_lines(
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
|
||||
let indent_char = stylist.indentation().as_char();
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_missing_whitespace =
|
||||
autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_missing_whitespace = false;
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_whitespace_before_parameters =
|
||||
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_whitespace_before_parameters = false;
|
||||
|
||||
let mut prev_line = None;
|
||||
let mut prev_indent_level = None;
|
||||
for line in iter_logical_lines(tokens, locator) {
|
||||
if line.mapping.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let indent_char = stylist.indentation().as_char();
|
||||
|
||||
// Extract the indentation level.
|
||||
let start_loc = line.mapping[0].1;
|
||||
let start_line = locator.slice(Range::new(Location::new(start_loc.row(), 0), start_loc));
|
||||
let indent_level = expand_indent(start_line);
|
||||
let indent_size = 4;
|
||||
|
||||
// Generate mapping from logical to physical offsets.
|
||||
let mapping_offsets = line.mapping.iter().map(|(offset, _)| *offset).collect_vec();
|
||||
|
||||
if line.flags.contains(TokenFlags::OPERATOR) {
|
||||
for (index, kind) in space_around_operator(&line.text) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
for line in &LogicalLines::from_tokens(tokens, locator) {
|
||||
if line.flags().contains(TokenFlags::OPERATOR) {
|
||||
for (location, kind) in space_around_operator(&line) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (location, kind) in whitespace_around_named_parameter_equals(&line.tokens()) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (location, kind) in missing_whitespace_around_operator(&line.tokens()) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for diagnostic in missing_whitespace(&line, should_fix_missing_whitespace) {
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
if line
|
||||
.flags
|
||||
.flags()
|
||||
.contains(TokenFlags::OPERATOR | TokenFlags::PUNCTUATION)
|
||||
{
|
||||
for (index, kind) in extraneous_whitespace(&line.text) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
for (location, kind) in extraneous_whitespace(&line) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if line.flags.contains(TokenFlags::KEYWORD) {
|
||||
for (index, kind) in whitespace_around_keywords(&line.text) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
if line.flags().contains(TokenFlags::KEYWORD) {
|
||||
for (location, kind) in whitespace_around_keywords(&line) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (location, kind) in missing_whitespace_after_keyword(&line.tokens) {
|
||||
for (location, kind) in missing_whitespace_after_keyword(&line.tokens()) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if line.flags.contains(TokenFlags::COMMENT) {
|
||||
for (range, kind) in whitespace_before_comment(&line.tokens, locator) {
|
||||
if line.flags().contains(TokenFlags::COMMENT) {
|
||||
for (range, kind) in whitespace_before_comment(&line.tokens(), locator) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location: range.location,
|
||||
end_location: range.end_location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if line.flags.contains(TokenFlags::OPERATOR) {
|
||||
for (location, kind) in
|
||||
whitespace_around_named_parameter_equals(&line.tokens, &line.text)
|
||||
{
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (location, kind) in missing_whitespace_around_operator(&line.tokens) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let should_fix = false;
|
||||
|
||||
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
|
||||
if line.flags().contains(TokenFlags::BRACKET) {
|
||||
for diagnostic in whitespace_before_parameters(
|
||||
&line.tokens(),
|
||||
should_fix_whitespace_before_parameters,
|
||||
) {
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if line.flags.contains(TokenFlags::BRACKET) {
|
||||
#[cfg(debug_assertions)]
|
||||
let should_fix =
|
||||
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
// Extract the indentation level.
|
||||
let Some(start_loc) = line.first_token_location() else { continue; };
|
||||
let start_line = locator.slice(Range::new(Location::new(start_loc.row(), 0), start_loc));
|
||||
let indent_level = expand_indent(start_line);
|
||||
let indent_size = 4;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let should_fix = false;
|
||||
|
||||
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, kind) in indentation(
|
||||
for (location, kind) in indentation(
|
||||
&line,
|
||||
prev_line.as_ref(),
|
||||
indent_char,
|
||||
@@ -201,20 +180,18 @@ pub fn check_logical_lines(
|
||||
prev_indent_level,
|
||||
indent_size,
|
||||
) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
fix: Fix::empty(),
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_comment() {
|
||||
if !line.is_comment_only() {
|
||||
prev_line = Some(line);
|
||||
prev_indent_level = Some(indent_level);
|
||||
}
|
||||
@@ -227,10 +204,9 @@ mod tests {
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{lexer, Mode};
|
||||
|
||||
use crate::rules::pycodestyle::rules::logical_lines::LogicalLines;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::checkers::logical_lines::iter_logical_lines;
|
||||
|
||||
#[test]
|
||||
fn split_logical_lines() {
|
||||
let contents = r#"
|
||||
@@ -239,9 +215,9 @@ y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = 1".to_string(),
|
||||
@@ -260,12 +236,12 @@ y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = [1, 2, 3, ]".to_string(),
|
||||
"x = [\n 1,\n 2,\n 3,\n]".to_string(),
|
||||
"y = 2".to_string(),
|
||||
"z = x + 1".to_string(),
|
||||
];
|
||||
@@ -274,11 +250,11 @@ z = x + 1"#;
|
||||
let contents = "x = 'abc'";
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec!["x = \"xxx\"".to_string()];
|
||||
let expected = vec!["x = 'abc'".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
@@ -287,9 +263,9 @@ def f():
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec!["def f():", "x = 1", "f()"];
|
||||
assert_eq!(actual, expected);
|
||||
@@ -302,11 +278,17 @@ def f():
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec!["def f():", "\"xxxxxxxxxxxxxxxxxxxx\"", "", "x = 1", "f()"];
|
||||
let expected = vec![
|
||||
"def f():",
|
||||
"\"\"\"Docstring goes here.\"\"\"",
|
||||
"",
|
||||
"x = 1",
|
||||
"f()",
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
pub mod ast;
|
||||
pub mod filesystem;
|
||||
pub mod imports;
|
||||
pub mod logical_lines;
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub(crate) mod logical_lines;
|
||||
pub mod noqa;
|
||||
pub mod physical_lines;
|
||||
pub mod tokens;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
//! `NoQA` enforcement and validation.
|
||||
|
||||
use log::warn;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit};
|
||||
use ruff_python_ast::newlines::StrExt;
|
||||
use ruff_python_ast::types::Range;
|
||||
|
||||
use crate::codes::NoqaCode;
|
||||
use crate::noqa;
|
||||
use crate::noqa::{extract_file_exemption, Directive, Exemption};
|
||||
use crate::noqa::{Directive, FileExemption};
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
@@ -26,92 +25,79 @@ pub fn check_noqa(
|
||||
) -> Vec<usize> {
|
||||
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
let mut file_exempted = false;
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let mut file_exemptions: Vec<NoqaCode> = vec![];
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let exemption = noqa::file_exemption(&lines, commented_lines);
|
||||
|
||||
// Map from line number to `noqa` directive on that line, along with any codes
|
||||
// that were matched by the directive.
|
||||
let mut noqa_directives: IntMap<usize, (Directive, Vec<NoqaCode>)> = IntMap::default();
|
||||
|
||||
// Indices of diagnostics that were ignored by a `noqa` directive.
|
||||
let mut ignored_diagnostics = vec![];
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
for lineno in commented_lines {
|
||||
match extract_file_exemption(lines[lineno - 1]) {
|
||||
Exemption::All => {
|
||||
file_exempted = true;
|
||||
}
|
||||
Exemption::Codes(codes) => {
|
||||
file_exemptions.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
Exemption::None => {}
|
||||
}
|
||||
|
||||
if enforce_noqa {
|
||||
// Extract all `noqa` directives.
|
||||
if enforce_noqa {
|
||||
for lineno in commented_lines {
|
||||
noqa_directives
|
||||
.entry(lineno - 1)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[lineno - 1]), vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
// Indices of diagnostics that were ignored by a `noqa` directive.
|
||||
let mut ignored_diagnostics = vec![];
|
||||
|
||||
// Remove any ignored diagnostics.
|
||||
for (index, diagnostic) in diagnostics.iter().enumerate() {
|
||||
if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the file is exempted, ignore all diagnostics.
|
||||
if file_exempted {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the diagnostic is ignored by a global exemption, ignore it.
|
||||
if !file_exemptions.is_empty() {
|
||||
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
match &exemption {
|
||||
FileExemption::All => {
|
||||
// If the file is exempted, ignore all diagnostics.
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, ignore it.
|
||||
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FileExemption::None => {}
|
||||
}
|
||||
|
||||
let diagnostic_lineno = diagnostic.location.row();
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
if let Some(parent_lineno) = diagnostic.parent.map(|location| location.row()) {
|
||||
let noqa_lineno = noqa_line_for.get(&parent_lineno).unwrap_or(&parent_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
let noqa = noqa_directives.entry(noqa_lineno - 1).or_insert_with(|| {
|
||||
(noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![])
|
||||
});
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(diagnostic.kind.rule().noqa_code());
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
(Directive::Codes(.., codes, _), matches) => {
|
||||
if noqa::includes(diagnostic.kind.rule(), codes) {
|
||||
if parent_lineno != diagnostic_lineno {
|
||||
let noqa_lineno = noqa_line_for.get(&parent_lineno).unwrap_or(&parent_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
let noqa = noqa_directives.entry(noqa_lineno - 1).or_insert_with(|| {
|
||||
(noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![])
|
||||
});
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(diagnostic.kind.rule().noqa_code());
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
(Directive::Codes(.., codes, _), matches) => {
|
||||
if noqa::includes(diagnostic.kind.rule(), codes) {
|
||||
matches.push(diagnostic.kind.rule().noqa_code());
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(Directive::None, ..) => {}
|
||||
}
|
||||
(Directive::None, ..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the diagnostic ignored by a `noqa` directive on the same line?
|
||||
let diagnostic_lineno = diagnostic.location.row();
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(&diagnostic_lineno)
|
||||
.unwrap_or(&diagnostic_lineno);
|
||||
@@ -155,7 +141,7 @@ pub fn check_noqa(
|
||||
),
|
||||
);
|
||||
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(delete_noqa(
|
||||
diagnostic.set_fix(delete_noqa(
|
||||
row,
|
||||
lines[row],
|
||||
leading_spaces,
|
||||
@@ -231,7 +217,7 @@ pub fn check_noqa(
|
||||
);
|
||||
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic.amend(delete_noqa(
|
||||
diagnostic.set_fix(delete_noqa(
|
||||
row,
|
||||
lines[row],
|
||||
leading_spaces,
|
||||
@@ -240,7 +226,7 @@ pub fn check_noqa(
|
||||
trailing_spaces,
|
||||
));
|
||||
} else {
|
||||
diagnostic.amend(Fix::replacement(
|
||||
diagnostic.set_fix(Edit::replacement(
|
||||
format!("# noqa: {}", valid_codes.join(", ")),
|
||||
Location::new(row + 1, start_char),
|
||||
Location::new(row + 1, end_char),
|
||||
@@ -259,7 +245,7 @@ pub fn check_noqa(
|
||||
ignored_diagnostics
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to delete a `noqa` directive.
|
||||
/// Generate a [`Edit`] to delete a `noqa` directive.
|
||||
fn delete_noqa(
|
||||
row: usize,
|
||||
line: &str,
|
||||
@@ -267,15 +253,15 @@ fn delete_noqa(
|
||||
start_byte: usize,
|
||||
end_byte: usize,
|
||||
trailing_spaces: usize,
|
||||
) -> Fix {
|
||||
) -> Edit {
|
||||
if start_byte - leading_spaces == 0 && end_byte == line.len() {
|
||||
// Ex) `# noqa`
|
||||
Fix::deletion(Location::new(row + 1, 0), Location::new(row + 2, 0))
|
||||
Edit::deletion(Location::new(row + 1, 0), Location::new(row + 2, 0))
|
||||
} else if end_byte == line.len() {
|
||||
// Ex) `x = 1 # noqa`
|
||||
let start_char = line[..start_byte].chars().count();
|
||||
let end_char = start_char + line[start_byte..end_byte].chars().count();
|
||||
Fix::deletion(
|
||||
Edit::deletion(
|
||||
Location::new(row + 1, start_char - leading_spaces),
|
||||
Location::new(row + 1, end_char + trailing_spaces),
|
||||
)
|
||||
@@ -283,7 +269,7 @@ fn delete_noqa(
|
||||
// Ex) `x = 1 # noqa # type: ignore`
|
||||
let start_char = line[..start_byte].chars().count();
|
||||
let end_char = start_char + line[start_byte..end_byte].chars().count();
|
||||
Fix::deletion(
|
||||
Edit::deletion(
|
||||
Location::new(row + 1, start_char),
|
||||
Location::new(row + 1, end_char + trailing_spaces),
|
||||
)
|
||||
@@ -291,7 +277,7 @@ fn delete_noqa(
|
||||
// Ex) `x = 1 # noqa here`
|
||||
let start_char = line[..start_byte].chars().count();
|
||||
let end_char = start_char + line[start_byte..end_byte].chars().count();
|
||||
Fix::deletion(
|
||||
Edit::deletion(
|
||||
Location::new(row + 1, start_char + 1 + 1),
|
||||
Location::new(row + 1, end_char + trailing_spaces),
|
||||
)
|
||||
|
||||
@@ -182,6 +182,8 @@ pub fn check_physical_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustpython_parser::lexer::lex;
|
||||
use rustpython_parser::Mode;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
@@ -195,7 +197,8 @@ mod tests {
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let locator = Locator::new(line);
|
||||
let stylist = Stylist::from_contents(line, &locator);
|
||||
let tokens: Vec<_> = lex(line, Mode::Module).collect();
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
check_physical_lines(
|
||||
@@ -211,7 +214,7 @@ mod tests {
|
||||
flags::Autofix::Enabled,
|
||||
)
|
||||
};
|
||||
assert!(!check_with_max_line_length(6).is_empty());
|
||||
assert!(check_with_max_line_length(7).is_empty());
|
||||
assert_eq!(check_with_max_line_length(8), vec![]);
|
||||
assert_eq!(check_with_max_line_length(8), vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,67 +26,67 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
Some(match (linter, code) {
|
||||
// pycodestyle errors
|
||||
(Pycodestyle, "E101") => Rule::MixedSpacesAndTabs,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E111") => Rule::IndentationWithInvalidMultiple,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E112") => Rule::NoIndentedBlock,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E113") => Rule::UnexpectedIndentation,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E114") => Rule::IndentationWithInvalidMultipleComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E115") => Rule::NoIndentedBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E116") => Rule::UnexpectedIndentationComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E117") => Rule::OverIndented,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E201") => Rule::WhitespaceAfterOpenBracket,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E202") => Rule::WhitespaceBeforeCloseBracket,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E223") => Rule::TabBeforeOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E224") => Rule::TabAfterOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E225") => Rule::MissingWhitespaceAroundOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E226") => Rule::MissingWhitespaceAroundArithmeticOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E227") => Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E228") => Rule::MissingWhitespaceAroundModuloOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E231") => Rule::MissingWhitespace,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E265") => Rule::NoSpaceAfterBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E266") => Rule::MultipleLeadingHashesForBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E271") => Rule::MultipleSpacesAfterKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E272") => Rule::MultipleSpacesBeforeKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E273") => Rule::TabAfterKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E274") => Rule::TabBeforeKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
(Pycodestyle, "E275") => Rule::MissingWhitespaceAfterKeyword,
|
||||
(Pycodestyle, "E401") => Rule::MultipleImportsOnOneLine,
|
||||
(Pycodestyle, "E402") => Rule::ModuleImportNotAtTopOfFile,
|
||||
@@ -168,7 +168,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Pylint, "E0101") => Rule::ReturnInInit,
|
||||
(Pylint, "E0116") => Rule::ContinueInFinally,
|
||||
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
|
||||
(Pylint, "E0118") => Rule::UsePriorToGlobalDeclaration,
|
||||
(Pylint, "E0118") => Rule::LoadBeforeGlobalDeclaration,
|
||||
(Pylint, "E0604") => Rule::InvalidAllObject,
|
||||
(Pylint, "E0605") => Rule::InvalidAllFormat,
|
||||
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
|
||||
@@ -238,6 +238,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Bugbear, "028") => Rule::NoExplicitStacklevel,
|
||||
(Flake8Bugbear, "029") => Rule::ExceptWithEmptyTuple,
|
||||
(Flake8Bugbear, "030") => Rule::ExceptWithNonExceptionClasses,
|
||||
(Flake8Bugbear, "031") => Rule::ReuseOfGroupbyGenerator,
|
||||
(Flake8Bugbear, "032") => Rule::UnintentionalTypeAnnotation,
|
||||
(Flake8Bugbear, "904") => Rule::RaiseWithoutFromInsideExcept,
|
||||
(Flake8Bugbear, "905") => Rule::ZipWithoutExplicitStrict,
|
||||
@@ -283,6 +284,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Return, "507") => Rule::SuperfluousElseContinue,
|
||||
(Flake8Return, "508") => Rule::SuperfluousElseBreak,
|
||||
|
||||
// flake8-gettext
|
||||
(Flake8GetText, "001") => Rule::FStringInGetTextFuncCall,
|
||||
(Flake8GetText, "002") => Rule::FormatInGetTextFuncCall,
|
||||
(Flake8GetText, "003") => Rule::PrintfInGetTextFuncCall,
|
||||
|
||||
// flake8-implicit-str-concat
|
||||
(Flake8ImplicitStrConcat, "001") => Rule::SingleLineImplicitStringConcatenation,
|
||||
(Flake8ImplicitStrConcat, "002") => Rule::MultiLineImplicitStringConcatenation,
|
||||
@@ -327,7 +333,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
|
||||
(Flake8Simplify, "102") => Rule::CollapsibleIf,
|
||||
(Flake8Simplify, "103") => Rule::NeedlessBool,
|
||||
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
|
||||
(Flake8Simplify, "105") => Rule::SuppressibleException,
|
||||
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
|
||||
(Flake8Simplify, "108") => Rule::IfElseBlockInsteadOfIfExp,
|
||||
(Flake8Simplify, "109") => Rule::CompareWithTuple,
|
||||
@@ -350,6 +356,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Simplify, "223") => Rule::ExprAndFalse,
|
||||
(Flake8Simplify, "300") => Rule::YodaConditions,
|
||||
(Flake8Simplify, "401") => Rule::IfElseBlockInsteadOfDictGet,
|
||||
(Flake8Simplify, "910") => Rule::DictGetWithNoneDefault,
|
||||
|
||||
// pyupgrade
|
||||
(Pyupgrade, "001") => Rule::UselessMetaclassType,
|
||||
@@ -470,16 +477,37 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Bandit, "105") => Rule::HardcodedPasswordString,
|
||||
(Flake8Bandit, "106") => Rule::HardcodedPasswordFuncArg,
|
||||
(Flake8Bandit, "107") => Rule::HardcodedPasswordDefault,
|
||||
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
|
||||
(Flake8Bandit, "108") => Rule::HardcodedTempFile,
|
||||
(Flake8Bandit, "110") => Rule::TryExceptPass,
|
||||
(Flake8Bandit, "112") => Rule::TryExceptContinue,
|
||||
(Flake8Bandit, "113") => Rule::RequestWithoutTimeout,
|
||||
(Flake8Bandit, "301") => Rule::SuspiciousPickleUsage,
|
||||
(Flake8Bandit, "302") => Rule::SuspiciousMarshalUsage,
|
||||
(Flake8Bandit, "303") => Rule::SuspiciousInsecureHashUsage,
|
||||
(Flake8Bandit, "304") => Rule::SuspiciousInsecureCipherUsage,
|
||||
(Flake8Bandit, "305") => Rule::SuspiciousInsecureCipherModeUsage,
|
||||
(Flake8Bandit, "306") => Rule::SuspiciousMktempUsage,
|
||||
(Flake8Bandit, "307") => Rule::SuspiciousEvalUsage,
|
||||
(Flake8Bandit, "308") => Rule::SuspiciousMarkSafeUsage,
|
||||
(Flake8Bandit, "310") => Rule::SuspiciousURLOpenUsage,
|
||||
(Flake8Bandit, "311") => Rule::SuspiciousNonCryptographicRandomUsage,
|
||||
(Flake8Bandit, "312") => Rule::SuspiciousTelnetUsage,
|
||||
(Flake8Bandit, "313") => Rule::SuspiciousXMLCElementTreeUsage,
|
||||
(Flake8Bandit, "314") => Rule::SuspiciousXMLElementTreeUsage,
|
||||
(Flake8Bandit, "315") => Rule::SuspiciousXMLExpatReaderUsage,
|
||||
(Flake8Bandit, "316") => Rule::SuspiciousXMLExpatBuilderUsage,
|
||||
(Flake8Bandit, "317") => Rule::SuspiciousXMLSaxUsage,
|
||||
(Flake8Bandit, "318") => Rule::SuspiciousXMLMiniDOMUsage,
|
||||
(Flake8Bandit, "319") => Rule::SuspiciousXMLPullDOMUsage,
|
||||
(Flake8Bandit, "320") => Rule::SuspiciousXMLETreeUsage,
|
||||
(Flake8Bandit, "321") => Rule::SuspiciousFTPLibUsage,
|
||||
(Flake8Bandit, "323") => Rule::SuspiciousUnverifiedContextUsage,
|
||||
(Flake8Bandit, "324") => Rule::HashlibInsecureHashFunction,
|
||||
(Flake8Bandit, "501") => Rule::RequestWithNoCertValidation,
|
||||
(Flake8Bandit, "506") => Rule::UnsafeYAMLLoad,
|
||||
(Flake8Bandit, "508") => Rule::SnmpInsecureVersion,
|
||||
(Flake8Bandit, "509") => Rule::SnmpWeakCryptography,
|
||||
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
|
||||
(Flake8Bandit, "612") => Rule::LoggingConfigInsecureListen,
|
||||
(Flake8Bandit, "701") => Rule::Jinja2AutoescapeFalse,
|
||||
|
||||
@@ -542,7 +570,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
|
||||
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
|
||||
(Flake8Pyi, "011") => Rule::TypedArgumentDefaultInStub,
|
||||
(Flake8Pyi, "012") => Rule::PassInClassBody,
|
||||
(Flake8Pyi, "014") => Rule::ArgumentDefaultInStub,
|
||||
(Flake8Pyi, "015") => Rule::AssignmentDefaultInStub,
|
||||
(Flake8Pyi, "021") => Rule::DocstringInStub,
|
||||
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
|
||||
|
||||
@@ -617,31 +647,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Tryceratops, "401") => Rule::VerboseLogMessage,
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => Rule::PathlibAbspath,
|
||||
(Flake8UsePathlib, "101") => Rule::PathlibChmod,
|
||||
(Flake8UsePathlib, "102") => Rule::PathlibMkdir,
|
||||
(Flake8UsePathlib, "103") => Rule::PathlibMakedirs,
|
||||
(Flake8UsePathlib, "104") => Rule::PathlibRename,
|
||||
(Flake8UsePathlib, "100") => Rule::OsPathAbspath,
|
||||
(Flake8UsePathlib, "101") => Rule::OsChmod,
|
||||
(Flake8UsePathlib, "102") => Rule::OsMkdir,
|
||||
(Flake8UsePathlib, "103") => Rule::OsMakedirs,
|
||||
(Flake8UsePathlib, "104") => Rule::OsRename,
|
||||
(Flake8UsePathlib, "105") => Rule::PathlibReplace,
|
||||
(Flake8UsePathlib, "106") => Rule::PathlibRmdir,
|
||||
(Flake8UsePathlib, "107") => Rule::PathlibRemove,
|
||||
(Flake8UsePathlib, "108") => Rule::PathlibUnlink,
|
||||
(Flake8UsePathlib, "109") => Rule::PathlibGetcwd,
|
||||
(Flake8UsePathlib, "110") => Rule::PathlibExists,
|
||||
(Flake8UsePathlib, "111") => Rule::PathlibExpanduser,
|
||||
(Flake8UsePathlib, "112") => Rule::PathlibIsDir,
|
||||
(Flake8UsePathlib, "113") => Rule::PathlibIsFile,
|
||||
(Flake8UsePathlib, "114") => Rule::PathlibIsLink,
|
||||
(Flake8UsePathlib, "115") => Rule::PathlibReadlink,
|
||||
(Flake8UsePathlib, "116") => Rule::PathlibStat,
|
||||
(Flake8UsePathlib, "117") => Rule::PathlibIsAbs,
|
||||
(Flake8UsePathlib, "118") => Rule::PathlibJoin,
|
||||
(Flake8UsePathlib, "119") => Rule::PathlibBasename,
|
||||
(Flake8UsePathlib, "120") => Rule::PathlibDirname,
|
||||
(Flake8UsePathlib, "121") => Rule::PathlibSamefile,
|
||||
(Flake8UsePathlib, "122") => Rule::PathlibSplitext,
|
||||
(Flake8UsePathlib, "123") => Rule::PathlibOpen,
|
||||
(Flake8UsePathlib, "124") => Rule::PathlibPyPath,
|
||||
(Flake8UsePathlib, "106") => Rule::OsRmdir,
|
||||
(Flake8UsePathlib, "107") => Rule::OsRemove,
|
||||
(Flake8UsePathlib, "108") => Rule::OsUnlink,
|
||||
(Flake8UsePathlib, "109") => Rule::OsGetcwd,
|
||||
(Flake8UsePathlib, "110") => Rule::OsPathExists,
|
||||
(Flake8UsePathlib, "111") => Rule::OsPathExpanduser,
|
||||
(Flake8UsePathlib, "112") => Rule::OsPathIsdir,
|
||||
(Flake8UsePathlib, "113") => Rule::OsPathIsfile,
|
||||
(Flake8UsePathlib, "114") => Rule::OsPathIslink,
|
||||
(Flake8UsePathlib, "115") => Rule::OsReadlink,
|
||||
(Flake8UsePathlib, "116") => Rule::OsStat,
|
||||
(Flake8UsePathlib, "117") => Rule::OsPathIsabs,
|
||||
(Flake8UsePathlib, "118") => Rule::OsPathJoin,
|
||||
(Flake8UsePathlib, "119") => Rule::OsPathBasename,
|
||||
(Flake8UsePathlib, "120") => Rule::OsPathDirname,
|
||||
(Flake8UsePathlib, "121") => Rule::OsPathSamefile,
|
||||
(Flake8UsePathlib, "122") => Rule::OsPathSplitext,
|
||||
(Flake8UsePathlib, "123") => Rule::BuiltinOpen,
|
||||
(Flake8UsePathlib, "124") => Rule::PyPath,
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => Rule::LoggingStringFormat,
|
||||
@@ -678,6 +708,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Django, "006") => Rule::DjangoExcludeWithModelForm,
|
||||
(Flake8Django, "007") => Rule::DjangoAllWithModelForm,
|
||||
(Flake8Django, "008") => Rule::DjangoModelWithoutDunderStr,
|
||||
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
|
||||
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
|
||||
|
||||
_ => return None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{
|
||||
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportFrom, Module, SimpleString,
|
||||
SmallStatement, Statement,
|
||||
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportAlias, ImportFrom,
|
||||
ImportNames, Module, SimpleString, SmallStatement, Statement,
|
||||
};
|
||||
|
||||
pub fn match_module(module_text: &str) -> Result<Module> {
|
||||
@@ -54,6 +54,16 @@ pub fn match_import_from<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut I
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_aliases<'a, 'b>(
|
||||
import_from: &'a mut ImportFrom<'b>,
|
||||
) -> Result<&'a mut Vec<ImportAlias<'b>>> {
|
||||
if let ImportNames::Aliases(aliases) = &mut import_from.names {
|
||||
Ok(aliases)
|
||||
} else {
|
||||
bail!("Expected ImportNames::Aliases")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_call<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Call<'b>> {
|
||||
if let Expression::Call(call) = expression {
|
||||
Ok(call)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_python_ast::visibility::{
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
|
||||
use ruff_python_semantic::analyze::visibility::{
|
||||
class_visibility, function_visibility, method_visibility, Modifier, Visibility, VisibleScope,
|
||||
};
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DefinitionKind<'a> {
|
||||
@@ -29,12 +30,13 @@ pub struct Docstring<'a> {
|
||||
pub indentation: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Documentable {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
pub fn transition_scope(scope: &VisibleScope, stmt: &Stmt, kind: &Documentable) -> VisibleScope {
|
||||
pub fn transition_scope(scope: VisibleScope, stmt: &Stmt, kind: Documentable) -> VisibleScope {
|
||||
match kind {
|
||||
Documentable::Function => VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use ruff_python_ast::visibility::{Modifier, VisibleScope};
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
|
||||
@@ -28,30 +28,30 @@ pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
|
||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||
pub fn extract<'a>(
|
||||
scope: &VisibleScope,
|
||||
scope: visibility::VisibleScope,
|
||||
stmt: &'a Stmt,
|
||||
body: &'a [Stmt],
|
||||
kind: &Documentable,
|
||||
kind: Documentable,
|
||||
) -> Definition<'a> {
|
||||
let expr = docstring_from(body);
|
||||
match kind {
|
||||
Documentable::Function => match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Function(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Class,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Method(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedFunction(stmt),
|
||||
@@ -59,22 +59,22 @@ pub fn extract<'a>(
|
||||
},
|
||||
},
|
||||
Documentable::Class => match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Class(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Class,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
|
||||
@@ -129,7 +129,7 @@ pub(crate) struct SectionContext<'a> {
|
||||
pub(crate) original_index: usize,
|
||||
}
|
||||
|
||||
fn suspected_as_section(line: &str, style: &SectionStyle) -> Option<SectionKind> {
|
||||
fn suspected_as_section(line: &str, style: SectionStyle) -> Option<SectionKind> {
|
||||
if let Some(kind) = SectionKind::from_str(whitespace::leading_words(line)) {
|
||||
if style.sections().contains(&kind) {
|
||||
return Some(kind);
|
||||
@@ -168,7 +168,7 @@ fn is_docstring_section(context: &SectionContext) -> bool {
|
||||
/// Extract all `SectionContext` values from a docstring.
|
||||
pub(crate) fn section_contexts<'a>(
|
||||
lines: &'a [&'a str],
|
||||
style: &SectionStyle,
|
||||
style: SectionStyle,
|
||||
) -> Vec<SectionContext<'a>> {
|
||||
let mut contexts = vec![];
|
||||
for (kind, lineno) in lines
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::docstrings::google::GOOGLE_SECTIONS;
|
||||
use crate::docstrings::numpy::NUMPY_SECTIONS;
|
||||
use crate::docstrings::sections::SectionKind;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum SectionStyle {
|
||||
Numpy,
|
||||
Google,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum FixMode {
|
||||
Generate,
|
||||
Apply,
|
||||
Diff,
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<bool> for FixMode {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
Self::Apply
|
||||
} else {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ pub fn convert(
|
||||
if let Some(src_paths) = &isort.src_paths {
|
||||
match options.src.as_mut() {
|
||||
Some(src) => {
|
||||
src.extend(src_paths.clone());
|
||||
src.extend_from_slice(src_paths);
|
||||
}
|
||||
None => {
|
||||
options.src = Some(src_paths.clone());
|
||||
|
||||
@@ -54,7 +54,7 @@ struct Token {
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum TokenType {
|
||||
Code,
|
||||
File,
|
||||
|
||||
@@ -7,7 +7,7 @@ use anyhow::anyhow;
|
||||
use crate::registry::Linter;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake82020,
|
||||
Flake8Annotations,
|
||||
|
||||
359
crates/ruff/src/importer.rs
Normal file
359
crates/ruff/src/importer.rs
Normal file
@@ -0,0 +1,359 @@
|
||||
//! Add and modify import statements to make module members available during fix execution.
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{Codegen, CodegenState, ImportAlias, Name, NameOrAttribute};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{Location, Stmt, StmtKind, Suite};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::imports::AnyImport;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use crate::cst::matchers::{match_aliases, match_import_from, match_module};
|
||||
|
||||
pub struct Importer<'a> {
|
||||
python_ast: &'a Suite,
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
/// A map from module name to top-level `StmtKind::ImportFrom` statements.
|
||||
import_from_map: FxHashMap<&'a str, &'a Stmt>,
|
||||
/// The last top-level import statement.
|
||||
trailing_import: Option<&'a Stmt>,
|
||||
}
|
||||
|
||||
impl<'a> Importer<'a> {
|
||||
pub fn new(python_ast: &'a Suite, locator: &'a Locator<'a>, stylist: &'a Stylist<'a>) -> Self {
|
||||
Self {
|
||||
python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
import_from_map: FxHashMap::default(),
|
||||
trailing_import: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit a top-level import statement.
|
||||
pub fn visit_import(&mut self, import: &'a Stmt) {
|
||||
// Store a reference to the import statement in the appropriate map.
|
||||
match &import.node {
|
||||
StmtKind::Import { .. } => {
|
||||
// Nothing to do here, we don't extend top-level `import` statements at all, so
|
||||
// no need to track them.
|
||||
}
|
||||
StmtKind::ImportFrom { module, level, .. } => {
|
||||
// Store a reverse-map from module name to `import ... from` statement.
|
||||
if level.map_or(true, |level| level == 0) {
|
||||
if let Some(module) = module {
|
||||
self.import_from_map.insert(module.as_str(), import);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Expected StmtKind::Import | StmtKind::ImportFrom");
|
||||
}
|
||||
}
|
||||
|
||||
// Store a reference to the last top-level import statement.
|
||||
self.trailing_import = Some(import);
|
||||
}
|
||||
|
||||
/// Add an import statement to import the given module.
|
||||
///
|
||||
/// If there are no existing imports, the new import will be added at the top
|
||||
/// of the file. Otherwise, it will be added after the most recent top-level
|
||||
/// import statement.
|
||||
pub fn add_import(&self, import: &AnyImport) -> Edit {
|
||||
let required_import = import.to_string();
|
||||
if let Some(stmt) = self.trailing_import {
|
||||
// Insert after the last top-level import.
|
||||
let Insertion {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
} = end_of_statement_insertion(stmt, self.locator, self.stylist);
|
||||
let content = format!("{prefix}{required_import}{suffix}");
|
||||
Edit::insertion(content, location)
|
||||
} else {
|
||||
// Insert at the top of the file.
|
||||
let Insertion {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
} = top_of_file_insertion(self.python_ast, self.locator, self.stylist);
|
||||
let content = format!("{prefix}{required_import}{suffix}");
|
||||
Edit::insertion(content, location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the top-level [`Stmt`] that imports the given module using `StmtKind::ImportFrom`.
|
||||
/// if it exists.
|
||||
pub fn get_import_from(&self, module: &str) -> Option<&Stmt> {
|
||||
self.import_from_map.get(module).copied()
|
||||
}
|
||||
|
||||
/// Add the given member to an existing `StmtKind::ImportFrom` statement.
|
||||
pub fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
|
||||
let mut tree = match_module(self.locator.slice(stmt))?;
|
||||
let import_from = match_import_from(&mut tree)?;
|
||||
let aliases = match_aliases(import_from)?;
|
||||
aliases.push(ImportAlias {
|
||||
name: NameOrAttribute::N(Box::new(Name {
|
||||
value: member,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
})),
|
||||
asname: None,
|
||||
comma: aliases.last().and_then(|alias| alias.comma.clone()),
|
||||
});
|
||||
let mut state = CodegenState {
|
||||
default_newline: &self.stylist.line_ending(),
|
||||
default_indent: self.stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
Ok(Edit::replacement(
|
||||
state.to_string(),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Insertion {
|
||||
/// The content to add before the insertion.
|
||||
prefix: &'static str,
|
||||
/// The location at which to insert.
|
||||
location: Location,
|
||||
/// The content to add after the insertion.
|
||||
suffix: &'static str,
|
||||
}
|
||||
|
||||
impl Insertion {
|
||||
fn new(prefix: &'static str, location: Location, suffix: &'static str) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the end of the last docstring.
|
||||
fn match_docstring_end(body: &[Stmt]) -> Option<Location> {
|
||||
let mut iter = body.iter();
|
||||
let Some(mut stmt) = iter.next() else {
|
||||
return None;
|
||||
};
|
||||
if !is_docstring_stmt(stmt) {
|
||||
return None;
|
||||
}
|
||||
for next in iter {
|
||||
if !is_docstring_stmt(next) {
|
||||
break;
|
||||
}
|
||||
stmt = next;
|
||||
}
|
||||
Some(stmt.end_location.unwrap())
|
||||
}
|
||||
|
||||
/// Find the location at which a "top-of-file" import should be inserted,
|
||||
/// along with a prefix and suffix to use for the insertion.
|
||||
///
|
||||
/// For example, given the following code:
|
||||
///
|
||||
/// ```python
|
||||
/// """Hello, world!"""
|
||||
///
|
||||
/// import os
|
||||
/// ```
|
||||
///
|
||||
/// The location returned will be the start of the `import os` statement,
|
||||
/// along with a trailing newline suffix.
|
||||
fn end_of_statement_insertion(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
|
||||
let location = stmt.end_location.unwrap();
|
||||
let mut tokens = lexer::lex_located(locator.skip(location), Mode::Module, location).flatten();
|
||||
if let Some((.., Tok::Semi, end)) = tokens.next() {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
Insertion::new(" ", end, ";")
|
||||
} else {
|
||||
// Otherwise, insert on the next line.
|
||||
Insertion::new(
|
||||
"",
|
||||
Location::new(location.row() + 1, 0),
|
||||
stylist.line_ending().as_str(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the location at which a "top-of-file" import should be inserted,
|
||||
/// along with a prefix and suffix to use for the insertion.
|
||||
///
|
||||
/// For example, given the following code:
|
||||
///
|
||||
/// ```python
|
||||
/// """Hello, world!"""
|
||||
///
|
||||
/// import os
|
||||
/// ```
|
||||
///
|
||||
/// The location returned will be the start of the `import os` statement,
|
||||
/// along with a trailing newline suffix.
|
||||
fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) -> Insertion {
|
||||
// Skip over any docstrings.
|
||||
let mut location = if let Some(location) = match_docstring_end(body) {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
let first_token = lexer::lex_located(locator.skip(location), Mode::Module, location)
|
||||
.flatten()
|
||||
.next();
|
||||
if let Some((.., Tok::Semi, end)) = first_token {
|
||||
return Insertion::new(" ", end, ";");
|
||||
}
|
||||
|
||||
// Otherwise, advance to the next row.
|
||||
Location::new(location.row() + 1, 0)
|
||||
} else {
|
||||
Location::default()
|
||||
};
|
||||
|
||||
// Skip over any comments and empty lines.
|
||||
for (.., tok, end) in
|
||||
lexer::lex_located(locator.skip(location), Mode::Module, location).flatten()
|
||||
{
|
||||
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
|
||||
location = Location::new(end.row() + 1, 0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Insertion::new("", location, stylist.line_ending().as_str());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use ruff_python_ast::source_code::{LineEnding, Locator, Stylist};
|
||||
|
||||
use crate::importer::{top_of_file_insertion, Insertion};
|
||||
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(top_of_file_insertion(&program, &locator, &stylist))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_of_file_insertions() -> Result<()> {
|
||||
let contents = "";
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(1, 0), LineEnding::default().as_str())
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!""""#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(2, 0), LineEnding::default().as_str())
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(2, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""
|
||||
"""Hello, world!"""
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(3, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
x = 1
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(1, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
#!/usr/bin/env python3
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(2, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
#!/usr/bin/env python3
|
||||
"""Hello, world!"""
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(3, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""
|
||||
#!/usr/bin/env python3
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(3, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""%s""" % "Hello, world!"
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", Location::new(1, 0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""; x = 1
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new(" ", Location::new(1, 20), ";")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""; x = 1; y = \
|
||||
2
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new(" ", Location::new(1, 20), ";")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ pub enum CodemirrorMode {
|
||||
}
|
||||
|
||||
/// String identifying the type of cell.
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Copy, Clone)]
|
||||
pub enum CellType {
|
||||
#[serde(rename = "code")]
|
||||
Code,
|
||||
@@ -236,14 +236,14 @@ pub enum CellType {
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum ScrolledEnum {
|
||||
#[serde(rename = "auto")]
|
||||
Auto,
|
||||
}
|
||||
|
||||
/// Type of cell output.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum OutputType {
|
||||
#[serde(rename = "display_data")]
|
||||
DisplayData,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use rustpython_parser::Tok;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Copy, Clone)]
|
||||
enum State {
|
||||
// Start of the module: first string gets marked as a docstring.
|
||||
#[default]
|
||||
@@ -32,86 +32,91 @@ pub struct StateMachine {
|
||||
|
||||
impl StateMachine {
|
||||
pub fn consume(&mut self, tok: &Tok) -> bool {
|
||||
if matches!(
|
||||
tok,
|
||||
Tok::NonLogicalNewline | Tok::Newline | Tok::Indent | Tok::Dedent | Tok::Comment(..)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
match tok {
|
||||
Tok::NonLogicalNewline
|
||||
| Tok::Newline
|
||||
| Tok::Indent
|
||||
| Tok::Dedent
|
||||
| Tok::Comment(..) => 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;
|
||||
Tok::String { .. } => {
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Tok::Class => {
|
||||
self.state = State::ExpectClassColon;
|
||||
self.bracket_count = 0;
|
||||
|
||||
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;
|
||||
false
|
||||
}
|
||||
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;
|
||||
Tok::Def => {
|
||||
self.state = State::ExpectFunctionColon;
|
||||
self.bracket_count = 0;
|
||||
|
||||
false
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Lpar | Tok::Lbrace | Tok::Lsqb => {
|
||||
self.bracket_count += 1;
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Rpar | Tok::Rbrace | Tok::Rsqb => {
|
||||
self.bracket_count -= 1;
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
_ => {
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ mod cst;
|
||||
pub mod directives;
|
||||
mod doc_lines;
|
||||
mod docstrings;
|
||||
pub mod fix;
|
||||
pub mod flake8_to_ruff;
|
||||
pub mod fs;
|
||||
mod importer;
|
||||
pub mod jupyter;
|
||||
mod lex;
|
||||
pub mod linter;
|
||||
|
||||
@@ -9,6 +9,7 @@ use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::ParseError;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
|
||||
@@ -16,14 +17,13 @@ use crate::autofix::fix_file;
|
||||
use crate::checkers::ast::check_ast;
|
||||
use crate::checkers::filesystem::check_file_path;
|
||||
use crate::checkers::imports::check_imports;
|
||||
use crate::checkers::logical_lines::check_logical_lines;
|
||||
use crate::checkers::noqa::check_noqa;
|
||||
use crate::checkers::physical_lines::check_physical_lines;
|
||||
use crate::checkers::tokens::check_tokens;
|
||||
use crate::directives::Directives;
|
||||
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||
use crate::message::{Message, Source};
|
||||
use crate::noqa::{add_noqa, rule_is_ignored};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -52,6 +52,15 @@ impl<T> LinterResult<T> {
|
||||
|
||||
pub type FixTable = FxHashMap<Rule, usize>;
|
||||
|
||||
pub struct FixerResult<'a> {
|
||||
/// The result returned by the linter, after applying any fixes.
|
||||
pub result: LinterResult<(Vec<Message>, Option<ImportMap>)>,
|
||||
/// The resulting source code, after applying any fixes.
|
||||
pub transformed: Cow<'a, str>,
|
||||
/// The number of fixes applied for each [`Rule`].
|
||||
pub fixed: FixTable,
|
||||
}
|
||||
|
||||
/// Generate `Diagnostic`s from the source code contents at the
|
||||
/// given `Path`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -67,9 +76,10 @@ pub fn check_path(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
autofix: flags::Autofix,
|
||||
) -> LinterResult<Vec<Diagnostic>> {
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
let mut imports = None;
|
||||
let mut error = None;
|
||||
|
||||
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
|
||||
@@ -105,7 +115,8 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_logical_lines())
|
||||
{
|
||||
diagnostics.extend(check_logical_lines(
|
||||
#[cfg(feature = "logical_lines")]
|
||||
diagnostics.extend(crate::checkers::logical_lines::check_logical_lines(
|
||||
&tokens,
|
||||
locator,
|
||||
stylist,
|
||||
@@ -142,7 +153,7 @@ pub fn check_path(
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
diagnostics.extend(check_imports(
|
||||
let (import_diagnostics, module_imports) = check_imports(
|
||||
&python_ast,
|
||||
locator,
|
||||
indexer,
|
||||
@@ -152,27 +163,22 @@ pub fn check_path(
|
||||
autofix,
|
||||
path,
|
||||
package,
|
||||
));
|
||||
);
|
||||
imports = module_imports;
|
||||
diagnostics.extend(import_diagnostics);
|
||||
}
|
||||
if use_doc_lines {
|
||||
doc_lines.extend(doc_lines_from_ast(&python_ast));
|
||||
}
|
||||
}
|
||||
Err(parse_error) => {
|
||||
if settings.rules.enabled(Rule::SyntaxError) {
|
||||
pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error);
|
||||
}
|
||||
|
||||
// If the syntax error is ignored, suppress it (regardless of whether
|
||||
// `Rule::SyntaxError` is enabled).
|
||||
if !rule_is_ignored(
|
||||
Rule::SyntaxError,
|
||||
parse_error.location.row(),
|
||||
&directives.noqa_line_for,
|
||||
locator,
|
||||
) {
|
||||
error = Some(parse_error);
|
||||
}
|
||||
// Always add a diagnostic for the syntax error, regardless of whether
|
||||
// `Rule::SyntaxError` is enabled. We avoid propagating the syntax error
|
||||
// if it's disabled via any of the usual mechanisms (e.g., `noqa`,
|
||||
// `per-file-ignores`), and the easiest way to detect that suppression is
|
||||
// to see if the diagnostic persists to the end of the function.
|
||||
pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error);
|
||||
error = Some(parse_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +236,24 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
LinterResult::new(diagnostics, error)
|
||||
// If there was a syntax error, check if it should be discarded.
|
||||
if error.is_some() {
|
||||
// If the syntax error was removed by _any_ of the above disablement methods (e.g., a
|
||||
// `noqa` directive, or a `per-file-ignore`), discard it.
|
||||
if !diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic.kind.rule() == Rule::SyntaxError)
|
||||
{
|
||||
error = None;
|
||||
}
|
||||
|
||||
// If the syntax error _diagnostic_ is disabled, discard the _diagnostic_.
|
||||
if !settings.rules.enabled(Rule::SyntaxError) {
|
||||
diagnostics.retain(|diagnostic| diagnostic.kind.rule() != Rule::SyntaxError);
|
||||
}
|
||||
}
|
||||
|
||||
LinterResult::new((diagnostics, imports), error)
|
||||
}
|
||||
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
@@ -247,7 +270,7 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
let locator = Locator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_contents(&contents, &locator);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer: Indexer = tokens.as_slice().into();
|
||||
@@ -287,7 +310,7 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
// Add any missing `# noqa` pragmas.
|
||||
add_noqa(
|
||||
path,
|
||||
&diagnostics,
|
||||
&diagnostics.0,
|
||||
&contents,
|
||||
indexer.commented_lines(),
|
||||
&directives.noqa_line_for,
|
||||
@@ -304,7 +327,7 @@ pub fn lint_only(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
autofix: flags::Autofix,
|
||||
) -> LinterResult<Vec<Message>> {
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
|
||||
@@ -312,7 +335,7 @@ pub fn lint_only(
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_contents(contents, &locator);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer: Indexer = tokens.as_slice().into();
|
||||
@@ -338,20 +361,23 @@ pub fn lint_only(
|
||||
|
||||
// Convert from diagnostics to messages.
|
||||
let path_lossy = path.to_string_lossy();
|
||||
result.map(|diagnostics| {
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_diagnostic(&diagnostic, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let lineno = diagnostic.location.row();
|
||||
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source, noqa_row)
|
||||
})
|
||||
.collect()
|
||||
result.map(|(messages, imports)| {
|
||||
(
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_diagnostic(&diagnostic, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let lineno = diagnostic.location.row();
|
||||
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source, noqa_row)
|
||||
})
|
||||
.collect(),
|
||||
imports,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -363,7 +389,7 @@ pub fn lint_fix<'a>(
|
||||
package: Option<&Path>,
|
||||
noqa: flags::Noqa,
|
||||
settings: &Settings,
|
||||
) -> Result<(LinterResult<Vec<Message>>, Cow<'a, str>, FixTable)> {
|
||||
) -> Result<FixerResult<'a>> {
|
||||
let mut transformed = Cow::Borrowed(contents);
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
@@ -384,7 +410,7 @@ pub fn lint_fix<'a>(
|
||||
let locator = Locator::new(&transformed);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_contents(&transformed, &locator);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer: Indexer = tokens.as_slice().into();
|
||||
@@ -438,7 +464,7 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
}
|
||||
|
||||
// Apply autofix.
|
||||
if let Some((fixed_contents, applied)) = fix_file(&result.data, &locator) {
|
||||
if let Some((fixed_contents, applied)) = fix_file(&result.data.0, &locator) {
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
for (rule, count) in applied {
|
||||
@@ -478,29 +504,33 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
|
||||
// Convert to messages.
|
||||
let path_lossy = path.to_string_lossy();
|
||||
return Ok((
|
||||
result.map(|diagnostics| {
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_diagnostic(&diagnostic, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let lineno = diagnostic.location.row();
|
||||
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
Message::from_diagnostic(
|
||||
diagnostic,
|
||||
path_lossy.to_string(),
|
||||
source,
|
||||
noqa_row,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
return Ok(FixerResult {
|
||||
result: result.map(|(messages, imports)| {
|
||||
(
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_diagnostic(&diagnostic, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let lineno = diagnostic.location.row();
|
||||
let noqa_row =
|
||||
*directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
Message::from_diagnostic(
|
||||
diagnostic,
|
||||
path_lossy.to_string(),
|
||||
source,
|
||||
noqa_row,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
imports,
|
||||
)
|
||||
}),
|
||||
transformed,
|
||||
fixed,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub struct Message {
|
||||
pub kind: DiagnosticKind,
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
pub fix: Option<Fix>,
|
||||
pub fix: Fix,
|
||||
pub filename: String,
|
||||
pub source: Option<Source>,
|
||||
pub noqa_row: usize,
|
||||
|
||||
@@ -28,49 +28,6 @@ static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Exemption<'a> {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<&'a str>),
|
||||
}
|
||||
|
||||
/// Return `true` if a file is exempt from checking based on the contents of the
|
||||
/// given line.
|
||||
pub fn extract_file_exemption(line: &str) -> Exemption {
|
||||
let line = line.trim_start();
|
||||
|
||||
if line.starts_with("# flake8: noqa")
|
||||
|| line.starts_with("# flake8: NOQA")
|
||||
|| line.starts_with("# flake8: NoQA")
|
||||
{
|
||||
return Exemption::All;
|
||||
}
|
||||
|
||||
if let Some(remainder) = line
|
||||
.strip_prefix("# ruff: noqa")
|
||||
.or_else(|| line.strip_prefix("# ruff: NOQA"))
|
||||
.or_else(|| line.strip_prefix("# ruff: NoQA"))
|
||||
{
|
||||
if remainder.is_empty() {
|
||||
return Exemption::All;
|
||||
} else if let Some(codes) = remainder.strip_prefix(':') {
|
||||
let codes: Vec<&str> = SPLIT_COMMA_REGEX
|
||||
.split(codes.trim())
|
||||
.map(str::trim)
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect();
|
||||
if codes.is_empty() {
|
||||
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
return Exemption::Codes(codes);
|
||||
}
|
||||
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
|
||||
Exemption::None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Directive<'a> {
|
||||
None,
|
||||
@@ -119,6 +76,47 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
}
|
||||
}
|
||||
|
||||
enum ParsedExemption<'a> {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<&'a str>),
|
||||
}
|
||||
|
||||
/// Return a [`ParsedExemption`] for a given comment line.
|
||||
fn parse_file_exemption(line: &str) -> ParsedExemption {
|
||||
let line = line.trim_start();
|
||||
|
||||
if line.starts_with("# flake8: noqa")
|
||||
|| line.starts_with("# flake8: NOQA")
|
||||
|| line.starts_with("# flake8: NoQA")
|
||||
{
|
||||
return ParsedExemption::All;
|
||||
}
|
||||
|
||||
if let Some(remainder) = line
|
||||
.strip_prefix("# ruff: noqa")
|
||||
.or_else(|| line.strip_prefix("# ruff: NOQA"))
|
||||
.or_else(|| line.strip_prefix("# ruff: NoQA"))
|
||||
{
|
||||
if remainder.is_empty() {
|
||||
return ParsedExemption::All;
|
||||
} else if let Some(codes) = remainder.strip_prefix(':') {
|
||||
let codes: Vec<&str> = SPLIT_COMMA_REGEX
|
||||
.split(codes.trim())
|
||||
.map(str::trim)
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect();
|
||||
if codes.is_empty() {
|
||||
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
return ParsedExemption::Codes(codes);
|
||||
}
|
||||
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
|
||||
}
|
||||
|
||||
ParsedExemption::None
|
||||
}
|
||||
|
||||
/// Returns `true` if the string list of `codes` includes `code` (or an alias
|
||||
/// thereof).
|
||||
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
|
||||
@@ -147,13 +145,50 @@ pub fn rule_is_ignored(
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileExemption {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<NoqaCode>),
|
||||
}
|
||||
|
||||
/// Extract the [`FileExemption`] for a given Python source file, enumerating any rules that are
|
||||
/// globally ignored within the file.
|
||||
pub fn file_exemption(lines: &[&str], commented_lines: &[usize]) -> FileExemption {
|
||||
let mut exempt_codes: Vec<NoqaCode> = vec![];
|
||||
|
||||
for lineno in commented_lines {
|
||||
match parse_file_exemption(lines[lineno - 1]) {
|
||||
ParsedExemption::All => {
|
||||
return FileExemption::All;
|
||||
}
|
||||
ParsedExemption::Codes(codes) => {
|
||||
exempt_codes.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
ParsedExemption::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
if exempt_codes.is_empty() {
|
||||
FileExemption::None
|
||||
} else {
|
||||
FileExemption::Codes(exempt_codes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
contents: &str,
|
||||
commented_lines: &[usize],
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
line_ending: &LineEnding,
|
||||
line_ending: LineEnding,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(
|
||||
diagnostics,
|
||||
@@ -171,71 +206,56 @@ fn add_noqa_inner(
|
||||
contents: &str,
|
||||
commented_lines: &[usize],
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
line_ending: &LineEnding,
|
||||
line_ending: LineEnding,
|
||||
) -> (usize, String) {
|
||||
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
|
||||
let mut matches_by_line: FxHashMap<usize, RuleSet> = FxHashMap::default();
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
let mut file_exempted = false;
|
||||
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let mut file_exemptions: Vec<NoqaCode> = vec![];
|
||||
|
||||
let lines: Vec<&str> = contents.universal_newlines().collect();
|
||||
for lineno in commented_lines {
|
||||
match extract_file_exemption(lines[lineno - 1]) {
|
||||
Exemption::All => {
|
||||
file_exempted = true;
|
||||
}
|
||||
Exemption::Codes(codes) => {
|
||||
file_exemptions.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
Exemption::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let exemption = file_exemption(&lines, commented_lines);
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for diagnostic in diagnostics {
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
if file_exempted {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if !file_exemptions.is_empty() {
|
||||
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
match &exemption {
|
||||
FileExemption::All => {
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FileExemption::None => {}
|
||||
}
|
||||
|
||||
let diagnostic_lineno = diagnostic.location.row();
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
if let Some(parent_lineno) = diagnostic.parent.map(|location| location.row()) {
|
||||
let noqa_lineno = noqa_line_for.get(&parent_lineno).unwrap_or(&parent_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
match extract_noqa_directive(lines[noqa_lineno - 1]) {
|
||||
Directive::All(..) => {
|
||||
continue;
|
||||
}
|
||||
Directive::Codes(.., codes, _) => {
|
||||
if includes(diagnostic.kind.rule(), &codes) {
|
||||
if parent_lineno != diagnostic_lineno {
|
||||
let noqa_lineno = noqa_line_for.get(&parent_lineno).unwrap_or(&parent_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
match extract_noqa_directive(lines[noqa_lineno - 1]) {
|
||||
Directive::All(..) => {
|
||||
continue;
|
||||
}
|
||||
Directive::Codes(.., codes, _) => {
|
||||
if includes(diagnostic.kind.rule(), &codes) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Directive::None => {}
|
||||
}
|
||||
Directive::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the diagnostic ignored by a `noqa` directive on the same line?
|
||||
let diagnostic_lineno = diagnostic.location.row();
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(&diagnostic_lineno)
|
||||
.unwrap_or(&diagnostic_lineno);
|
||||
@@ -268,7 +288,7 @@ fn add_noqa_inner(
|
||||
match matches_by_line.get(&lineno) {
|
||||
None => {
|
||||
output.push_str(line);
|
||||
output.push_str(line_ending);
|
||||
output.push_str(&line_ending);
|
||||
}
|
||||
Some(rules) => {
|
||||
match extract_noqa_directive(line) {
|
||||
@@ -281,13 +301,13 @@ fn add_noqa_inner(
|
||||
|
||||
// Add codes.
|
||||
push_codes(&mut output, rules.iter().map(|rule| rule.noqa_code()));
|
||||
output.push_str(line_ending);
|
||||
output.push_str(&line_ending);
|
||||
count += 1;
|
||||
}
|
||||
Directive::All(..) => {
|
||||
// Leave the line as-is.
|
||||
output.push_str(line);
|
||||
output.push_str(line_ending);
|
||||
output.push_str(&line_ending);
|
||||
}
|
||||
Directive::Codes(_, start_byte, _, existing, _) => {
|
||||
// Reconstruct the line based on the preserved rule codes.
|
||||
@@ -311,7 +331,7 @@ fn add_noqa_inner(
|
||||
);
|
||||
|
||||
output.push_str(&formatted);
|
||||
output.push_str(line_ending);
|
||||
output.push_str(&line_ending);
|
||||
|
||||
// Only count if the new line is an actual edit.
|
||||
if formatted != line {
|
||||
@@ -375,7 +395,7 @@ mod tests {
|
||||
contents,
|
||||
&commented_lines,
|
||||
&noqa_line_for,
|
||||
&LineEnding::Lf,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, format!("{contents}\n"));
|
||||
@@ -394,7 +414,7 @@ mod tests {
|
||||
contents,
|
||||
&commented_lines,
|
||||
&noqa_line_for,
|
||||
&LineEnding::Lf,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: F841\n");
|
||||
@@ -419,7 +439,7 @@ mod tests {
|
||||
contents,
|
||||
&commented_lines,
|
||||
&noqa_line_for,
|
||||
&LineEnding::Lf,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
||||
@@ -444,7 +464,7 @@ mod tests {
|
||||
contents,
|
||||
&commented_lines,
|
||||
&noqa_line_for,
|
||||
&LineEnding::Lf,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, "x = 1 # noqa\n");
|
||||
|
||||
@@ -14,68 +14,68 @@ pub use rule_set::{RuleSet, RuleSetIterator};
|
||||
ruff_macros::register_rules!(
|
||||
// pycodestyle errors
|
||||
rules::pycodestyle::rules::MixedSpacesAndTabs,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::IndentationWithInvalidMultiple,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::NoIndentedBlock,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::UnexpectedIndentation,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::IndentationWithInvalidMultipleComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::NoIndentedBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::UnexpectedIndentationComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::OverIndented,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::WhitespaceAfterOpenBracket,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::WhitespaceBeforeCloseBracket,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::WhitespaceBeforePunctuation,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MultipleSpacesBeforeOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MultipleSpacesAfterOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::TabBeforeOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::TabAfterOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::TooFewSpacesBeforeInlineComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::NoSpaceAfterInlineComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::NoSpaceAfterBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MultipleLeadingHashesForBlockComment,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespace,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAfterKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAroundOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAroundArithmeticOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAroundBitwiseOrShiftOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAroundModuloOperator,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::TabAfterKeyword,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::WhitespaceBeforeParameters,
|
||||
#[cfg(debug_assertions)]
|
||||
rules::pycodestyle::rules::TabBeforeKeyword,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::NoIndentedBlock,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::UnexpectedIndentation,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::OverIndented,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::TabBeforeOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::TabAfterOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespace,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::TabAfterKeyword,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
rules::pycodestyle::rules::logical_lines::TabBeforeKeyword,
|
||||
rules::pycodestyle::rules::MultipleImportsOnOneLine,
|
||||
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
|
||||
rules::pycodestyle::rules::LineTooLong,
|
||||
@@ -167,7 +167,7 @@ ruff_macros::register_rules!(
|
||||
rules::pylint::rules::UselessImportAlias,
|
||||
rules::pylint::rules::UnnecessaryDirectLambdaCall,
|
||||
rules::pylint::rules::NonlocalWithoutBinding,
|
||||
rules::pylint::rules::UsePriorToGlobalDeclaration,
|
||||
rules::pylint::rules::LoadBeforeGlobalDeclaration,
|
||||
rules::pylint::rules::AwaitOutsideAsync,
|
||||
rules::pylint::rules::PropertyWithParameters,
|
||||
rules::pylint::rules::ReturnInInit,
|
||||
@@ -223,6 +223,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_bugbear::rules::ZipWithoutExplicitStrict,
|
||||
rules::flake8_bugbear::rules::ExceptWithEmptyTuple,
|
||||
rules::flake8_bugbear::rules::ExceptWithNonExceptionClasses,
|
||||
rules::flake8_bugbear::rules::ReuseOfGroupbyGenerator,
|
||||
rules::flake8_bugbear::rules::UnintentionalTypeAnnotation,
|
||||
// flake8-blind-except
|
||||
rules::flake8_blind_except::rules::BlindExcept,
|
||||
@@ -299,7 +300,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_simplify::rules::DuplicateIsinstanceCall,
|
||||
rules::flake8_simplify::rules::CollapsibleIf,
|
||||
rules::flake8_simplify::rules::NeedlessBool,
|
||||
rules::flake8_simplify::rules::UseContextlibSuppress,
|
||||
rules::flake8_simplify::rules::SuppressibleException,
|
||||
rules::flake8_simplify::rules::ReturnInTryExceptFinally,
|
||||
rules::flake8_simplify::rules::IfElseBlockInsteadOfIfExp,
|
||||
rules::flake8_simplify::rules::CompareWithTuple,
|
||||
@@ -321,6 +322,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_simplify::rules::ExprAndFalse,
|
||||
rules::flake8_simplify::rules::YodaConditions,
|
||||
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet,
|
||||
rules::flake8_simplify::rules::DictGetWithNoneDefault,
|
||||
// pyupgrade
|
||||
rules::pyupgrade::rules::UselessMetaclassType,
|
||||
rules::pyupgrade::rules::TypeOfPrimitive,
|
||||
@@ -429,24 +431,45 @@ ruff_macros::register_rules!(
|
||||
rules::eradicate::rules::CommentedOutCode,
|
||||
// flake8-bandit
|
||||
rules::flake8_bandit::rules::Assert,
|
||||
rules::flake8_bandit::rules::ExecBuiltin,
|
||||
rules::flake8_bandit::rules::BadFilePermissions,
|
||||
rules::flake8_bandit::rules::ExecBuiltin,
|
||||
rules::flake8_bandit::rules::HardcodedBindAllInterfaces,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordString,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordDefault,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
|
||||
rules::flake8_bandit::rules::HardcodedPasswordString,
|
||||
rules::flake8_bandit::rules::HardcodedSQLExpression,
|
||||
rules::flake8_bandit::rules::HardcodedTempFile,
|
||||
rules::flake8_bandit::rules::TryExceptPass,
|
||||
rules::flake8_bandit::rules::TryExceptContinue,
|
||||
rules::flake8_bandit::rules::RequestWithoutTimeout,
|
||||
rules::flake8_bandit::rules::HashlibInsecureHashFunction,
|
||||
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
|
||||
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
|
||||
rules::flake8_bandit::rules::RequestWithNoCertValidation,
|
||||
rules::flake8_bandit::rules::UnsafeYAMLLoad,
|
||||
rules::flake8_bandit::rules::RequestWithoutTimeout,
|
||||
rules::flake8_bandit::rules::SnmpInsecureVersion,
|
||||
rules::flake8_bandit::rules::SnmpWeakCryptography,
|
||||
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
|
||||
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
|
||||
rules::flake8_bandit::rules::SuspiciousEvalUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousFTPLibUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureCipherUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureCipherModeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousInsecureHashUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMarkSafeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMarshalUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousMktempUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousNonCryptographicRandomUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousPickleUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousTelnetUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousURLOpenUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLCElementTreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLETreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLElementTreeUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLExpatBuilderUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLExpatReaderUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage,
|
||||
rules::flake8_bandit::rules::SuspiciousXMLSaxUsage,
|
||||
rules::flake8_bandit::rules::TryExceptContinue,
|
||||
rules::flake8_bandit::rules::TryExceptPass,
|
||||
rules::flake8_bandit::rules::UnsafeYAMLLoad,
|
||||
// flake8-boolean-trap
|
||||
rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition,
|
||||
rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition,
|
||||
@@ -492,16 +515,18 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_errmsg::rules::FStringInException,
|
||||
rules::flake8_errmsg::rules::DotFormatInException,
|
||||
// flake8-pyi
|
||||
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
||||
rules::flake8_pyi::rules::ArgumentDefaultInStub,
|
||||
rules::flake8_pyi::rules::AssignmentDefaultInStub,
|
||||
rules::flake8_pyi::rules::BadVersionInfoComparison,
|
||||
rules::flake8_pyi::rules::DocstringInStub,
|
||||
rules::flake8_pyi::rules::NonEmptyStubBody,
|
||||
rules::flake8_pyi::rules::PassStatementStubBody,
|
||||
rules::flake8_pyi::rules::TypeCommentInStub,
|
||||
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
|
||||
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
||||
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
||||
rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
||||
rules::flake8_pyi::rules::PassStatementStubBody,
|
||||
rules::flake8_pyi::rules::NonEmptyStubBody,
|
||||
rules::flake8_pyi::rules::DocstringInStub,
|
||||
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
|
||||
rules::flake8_pyi::rules::ArgumentDefaultInStub,
|
||||
rules::flake8_pyi::rules::TypeCommentInStub,
|
||||
rules::flake8_pyi::rules::PassInClassBody,
|
||||
// flake8-pytest-style
|
||||
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
|
||||
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
|
||||
@@ -566,31 +591,31 @@ ruff_macros::register_rules!(
|
||||
rules::tryceratops::rules::ErrorInsteadOfException,
|
||||
rules::tryceratops::rules::VerboseLogMessage,
|
||||
// flake8-use-pathlib
|
||||
rules::flake8_use_pathlib::violations::PathlibAbspath,
|
||||
rules::flake8_use_pathlib::violations::PathlibChmod,
|
||||
rules::flake8_use_pathlib::violations::PathlibMkdir,
|
||||
rules::flake8_use_pathlib::violations::PathlibMakedirs,
|
||||
rules::flake8_use_pathlib::violations::PathlibRename,
|
||||
rules::flake8_use_pathlib::violations::OsPathAbspath,
|
||||
rules::flake8_use_pathlib::violations::OsChmod,
|
||||
rules::flake8_use_pathlib::violations::OsMkdir,
|
||||
rules::flake8_use_pathlib::violations::OsMakedirs,
|
||||
rules::flake8_use_pathlib::violations::OsRename,
|
||||
rules::flake8_use_pathlib::violations::PathlibReplace,
|
||||
rules::flake8_use_pathlib::violations::PathlibRmdir,
|
||||
rules::flake8_use_pathlib::violations::PathlibRemove,
|
||||
rules::flake8_use_pathlib::violations::PathlibUnlink,
|
||||
rules::flake8_use_pathlib::violations::PathlibGetcwd,
|
||||
rules::flake8_use_pathlib::violations::PathlibExists,
|
||||
rules::flake8_use_pathlib::violations::PathlibExpanduser,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsDir,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsFile,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsLink,
|
||||
rules::flake8_use_pathlib::violations::PathlibReadlink,
|
||||
rules::flake8_use_pathlib::violations::PathlibStat,
|
||||
rules::flake8_use_pathlib::violations::PathlibIsAbs,
|
||||
rules::flake8_use_pathlib::violations::PathlibJoin,
|
||||
rules::flake8_use_pathlib::violations::PathlibBasename,
|
||||
rules::flake8_use_pathlib::violations::PathlibDirname,
|
||||
rules::flake8_use_pathlib::violations::PathlibSamefile,
|
||||
rules::flake8_use_pathlib::violations::PathlibSplitext,
|
||||
rules::flake8_use_pathlib::violations::PathlibOpen,
|
||||
rules::flake8_use_pathlib::violations::PathlibPyPath,
|
||||
rules::flake8_use_pathlib::violations::OsRmdir,
|
||||
rules::flake8_use_pathlib::violations::OsRemove,
|
||||
rules::flake8_use_pathlib::violations::OsUnlink,
|
||||
rules::flake8_use_pathlib::violations::OsGetcwd,
|
||||
rules::flake8_use_pathlib::violations::OsPathExists,
|
||||
rules::flake8_use_pathlib::violations::OsPathExpanduser,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsdir,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsfile,
|
||||
rules::flake8_use_pathlib::violations::OsPathIslink,
|
||||
rules::flake8_use_pathlib::violations::OsReadlink,
|
||||
rules::flake8_use_pathlib::violations::OsStat,
|
||||
rules::flake8_use_pathlib::violations::OsPathIsabs,
|
||||
rules::flake8_use_pathlib::violations::OsPathJoin,
|
||||
rules::flake8_use_pathlib::violations::OsPathBasename,
|
||||
rules::flake8_use_pathlib::violations::OsPathDirname,
|
||||
rules::flake8_use_pathlib::violations::OsPathSamefile,
|
||||
rules::flake8_use_pathlib::violations::OsPathSplitext,
|
||||
rules::flake8_use_pathlib::violations::BuiltinOpen,
|
||||
rules::flake8_use_pathlib::violations::PyPath,
|
||||
// flake8-logging-format
|
||||
rules::flake8_logging_format::violations::LoggingStringFormat,
|
||||
rules::flake8_logging_format::violations::LoggingPercentFormat,
|
||||
@@ -604,6 +629,10 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_raise::rules::UnnecessaryParenOnRaiseException,
|
||||
// flake8-self
|
||||
rules::flake8_self::rules::PrivateMemberAccess,
|
||||
// flake8-gettext
|
||||
rules::flake8_gettext::rules::FStringInGetTextFuncCall,
|
||||
rules::flake8_gettext::rules::FormatInGetTextFuncCall,
|
||||
rules::flake8_gettext::rules::PrintfInGetTextFuncCall,
|
||||
// numpy
|
||||
rules::numpy::rules::NumpyDeprecatedTypeAlias,
|
||||
rules::numpy::rules::NumpyLegacyRandom,
|
||||
@@ -621,6 +650,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_django::rules::DjangoExcludeWithModelForm,
|
||||
rules::flake8_django::rules::DjangoAllWithModelForm,
|
||||
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
|
||||
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
|
||||
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
|
||||
);
|
||||
|
||||
@@ -753,6 +783,9 @@ pub enum Linter {
|
||||
/// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
||||
#[prefix = "TCH"]
|
||||
Flake8TypeChecking,
|
||||
/// [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
#[prefix = "INT"]
|
||||
Flake8GetText,
|
||||
/// [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/)
|
||||
#[prefix = "ARG"]
|
||||
Flake8UnusedArguments,
|
||||
@@ -884,7 +917,7 @@ impl Rule {
|
||||
Rule::IOError => LintSource::Io,
|
||||
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
|
||||
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(feature = "logical_lines")]
|
||||
Rule::IndentationWithInvalidMultiple
|
||||
| Rule::IndentationWithInvalidMultipleComment
|
||||
| Rule::MissingWhitespace
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user