Compare commits

..

13 Commits

Author SHA1 Message Date
Charlie Marsh
4a7167520a Match pylint logic 2023-03-17 17:31:34 -04:00
Charlie Marsh
41ab37e792 Rebase 2023-03-17 17:07:51 -04:00
Charlie Marsh
0b1806fdfa Merge branch 'main' into useless_return 2023-03-17 17:04:37 -04:00
tomecki
d20474da87 fix for autofix 2023-02-25 13:12:27 +01:00
tomecki
b7df7ee370 Merge branch 'useless_return' of github.com:tomecki/ruff into useless_return 2023-02-25 13:08:38 +01:00
tomecki
d6f0d1fab2 Merge branch 'charliermarsh:main' into useless_return 2023-02-25 13:08:09 +01:00
tomecki
c9bf979e61 Merge branch 'main' into useless_return 2023-02-25 13:06:51 +01:00
tomecki
e556fb4c77 Update crates/ruff/src/rules/pylint/rules/useless_return.rs
Co-authored-by: Jeong YunWon <69878+youknowone@users.noreply.github.com>
2023-02-25 12:59:13 +01:00
tomecki
02ae14037b Merge branch 'main' into useless_return 2023-02-22 09:38:18 +01:00
tomecki
385ccb2a50 rename for clarity 2023-02-21 20:54:22 +01:00
tomecki
ecacccbd5e cleanup 2023-02-21 20:39:33 +01:00
tomecki
bd7b472902 Merge branch 'main' into useless_return 2023-02-21 20:28:48 +01:00
tomecki
89b1b06dc7 wip 2023-02-21 20:28:33 +01:00
1179 changed files with 19832 additions and 32979 deletions

View File

@@ -26,11 +26,4 @@ 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",
]

View File

@@ -26,7 +26,7 @@ jobs:
- name: "PR - Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
- name: "PR - Build benchmarks"
uses: actions-rs/cargo@v1
@@ -75,61 +75,59 @@ jobs:
- run-benchmark
steps:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: taiki-e/install-action@cargo-binstall
- name: "Install critcmp"
# Use debug build: Building takes much longer than the "slowness" of using the debug build.
run: cargo install --debug critcmp
- name: "Install critcmp"
run: cargo binstall critcmp -y
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
echo ${{ github.event.pull_request.number }} > pr-number
echo ${{ github.event.pull_request.number }} > pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md

View File

@@ -85,6 +85,7 @@ jobs:
name: ruff
path: target/debug/ruff
cargo-test-wasm:
runs-on: ubuntu-latest
name: "cargo test (wasm)"
@@ -110,16 +111,14 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup component add rustfmt
run: rustup show
- uses: Swatinem/rust-cache@v2
- run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --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 --prefix TST --code 001 --linter test
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
- run: cargo fmt --all --check
typos:
name: "spell check"
@@ -177,52 +176,3 @@ jobs:
with:
name: ecosystem-result
path: ecosystem-result
cargo-udeps:
name: "cargo udeps"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup toolchain install nightly
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
run: |
unused_dependencies=$(cargo +nightly 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 "Unused dependencies found" > $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
echo '```' >> $GITHUB_STEP_SUMMARY

View File

@@ -65,7 +65,6 @@ jobs:
then
echo "### Ecosystem" >> $GITHUB_OUTPUT
cat pr/ecosystem/ecosystem-result >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
fi
if [[ -f pr/benchmark/summary.md ]]

View File

@@ -28,17 +28,17 @@ repos:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
language: rust
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: system
language: rust
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run -p ruff_cli -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
language: rust
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -49,7 +49,7 @@ repos:
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: system
language: rust
pass_filenames: false
exclude: target

View File

@@ -145,4 +145,4 @@ default.
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.
See the [README](https://github.com/charliermarsh/ruff#pyprojecttoml-discovery) for more.

39
Cargo.lock generated
View File

@@ -132,6 +132,12 @@ 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"
@@ -774,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.260"
version = "0.0.256"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1976,13 +1982,15 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.260"
version = "0.0.256"
dependencies = [
"anyhow",
"bisection",
"bitflags",
"chrono",
"clap 4.1.8",
"colored",
"criterion",
"dirs",
"fern",
"glob",
@@ -2017,7 +2025,6 @@ dependencies = [
"schemars",
"semver",
"serde",
"serde_json",
"shellexpand",
"smallvec",
"strum",
@@ -2026,8 +2033,6 @@ dependencies = [
"textwrap",
"thiserror",
"toml",
"typed-arena",
"unicode-width",
]
[[package]]
@@ -2058,7 +2063,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.260"
version = "0.0.256"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2086,7 +2091,6 @@ dependencies = [
"ruff",
"ruff_cache",
"ruff_diagnostics",
"ruff_python_stdlib",
"rustc-hash",
"serde",
"serde_json",
@@ -2097,7 +2101,6 @@ dependencies = [
"tikv-jemallocator",
"ureq",
"walkdir",
"wild",
]
[[package]]
@@ -2127,8 +2130,6 @@ dependencies = [
name = "ruff_diagnostics"
version = "0.0.0"
dependencies = [
"anyhow",
"log",
"ruff_python_ast",
"rustpython-parser",
"serde",
@@ -2209,6 +2210,7 @@ name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
@@ -2252,7 +2254,6 @@ dependencies = [
"js-sys",
"log",
"ruff",
"ruff_diagnostics",
"ruff_python_ast",
"ruff_rustpython",
"rustpython-parser",
@@ -2504,7 +2505,6 @@ version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
@@ -2915,12 +2915,6 @@ 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"
@@ -3267,15 +3261,6 @@ 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"

View File

@@ -37,7 +37,7 @@ rustpython-parser = { features = [
], git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
serde_json = { version = "1.0.93" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1", features = ["strum_macros"] }
@@ -48,7 +48,10 @@ textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
[profile.release]
lto = "fat"
panic = "abort"
lto = "thin"
codegen-units = 1
opt-level = 3
[profile.dev.package.insta]
opt-level = 3

35
LICENSE
View File

@@ -195,15 +195,6 @@ 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)
@@ -402,6 +393,7 @@ are:
THE SOFTWARE.
"""
- autoflake, licensed as follows:
"""
Copyright (C) 2012-2018 Steven Myint
@@ -425,31 +417,6 @@ are:
SOFTWARE.
"""
- autotyping, licensed as follows:
"""
MIT License
Copyright (c) 2023 Jelle Zijlstra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Flake8, licensed as follows:
"""
== Flake8 License (MIT) ==

View File

@@ -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.260'
rev: 'v0.0.256'
hooks:
- id: ruff
```
@@ -160,7 +160,7 @@ select = ["E", "F"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
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"]
fixable = ["A", "B", "C", "D", "E", "F", "..."]
unfixable = []
# Exclude a variety of commonly ignored directories.
@@ -227,55 +227,6 @@ stylistic rules made obsolete by the use of an autoformatter, like
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
quality tools, including:
- [autoflake](https://pypi.org/project/autoflake/)
- [eradicate](https://pypi.org/project/eradicate/)
- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [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/)
- [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/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
- [flake8-print](https://pypi.org/project/flake8-print/)
- [flake8-pyi](https://pypi.org/project/flake8-pyi/)
- [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
- [flake8-quotes](https://pypi.org/project/flake8-quotes/)
- [flake8-raise](https://pypi.org/project/flake8-raise/)
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [isort](https://pypi.org/project/isort/)
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980))
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
@@ -329,7 +280,6 @@ Ruff is used in a number of major open-source projects, including:
- [Zulip](https://github.com/zulip/zulip)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Pydantic](https://github.com/pydantic/pydantic)
- [PostHog](https://github.com/PostHog/posthog)
- [Dagster](https://github.com/dagster-io/dagster)
- [Dagger](https://github.com/dagger/dagger)
- [Sphinx](https://github.com/sphinx-doc/sphinx)

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.260"
version = "0.0.256"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -85,9 +85,8 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
## License

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.260"
version = "0.0.256"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -22,6 +22,7 @@ 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 }
@@ -57,7 +58,6 @@ 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" }
strum = { workspace = true }
@@ -65,10 +65,9 @@ strum_macros = { workspace = true }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = {version ="0.1.10"}
[dev-dependencies]
criterion = { version = "0.4.0" }
insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
@@ -76,4 +75,3 @@ test-case = { workspace = true }
[features]
default = []
logical_lines = []
jupyter_notebook = []

View File

@@ -2,7 +2,3 @@ avoid-*
do-not-*
uses-*
*-used
rewrite-*
prefer-*
consider-*
use-*

View File

@@ -1,42 +0,0 @@
class Foo:
def __str__(self):
...
def __repr__(self):
...
def __len__(self):
...
def __length_hint__(self):
...
def __init__(self):
...
def __del__(self):
...
def __bool__(self):
...
def __bytes__(self):
...
def __format__(self, format_spec):
...
def __contains__(self, item):
...
def __complex__(self):
...
def __int__(self):
...
def __float__(self):
...
def __index__(self):
...

View File

@@ -1,3 +0,0 @@
import pickle
pickle.loads()

View File

@@ -1,3 +0,0 @@
from telnetlib import Telnet
Telnet("localhost", 23)

View File

@@ -1,9 +1,7 @@
import collections
import datetime as dt
from decimal import Decimal
import logging
import operator
from pathlib import Path
import random
import re
import time
@@ -167,26 +165,6 @@ 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()]):

View File

@@ -57,9 +57,3 @@ def foo3():
def foo4():
...
def foo5():
foo.bar # Attribute (raise)
object().__class__ # Attribute (raise)
"foo" + "bar" # BinOp (raise)

View File

@@ -1,112 +0,0 @@
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 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)

View File

@@ -1,19 +1,6 @@
x = [1, 2, 3]
y = [("a", 1), ("b", 2), ("c", 3)]
z = [(1,), (2,), (3,)]
d = {"a": 1, "b": 2, "c": 3}
[i for i in x]
{i for i in x}
{k: v for k, v in y}
{k: v for k, v in d.items()}
[i for i, in z]
[i for i, j in y]
[i for i in x if i > 1]
[i for i in x for j in x]
{v: k for k, v in y}
{k.foo: k for k in y}
{k["foo"]: k for k in y}
{k: v if v else None for k, v in y}

View File

@@ -1,113 +0,0 @@
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

View File

@@ -1 +0,0 @@
_(f"{'value'}")

View File

@@ -1 +0,0 @@
_("{}".format("line"))

View File

@@ -1 +0,0 @@
_("%s" % "line")

View File

@@ -1,9 +1,3 @@
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)

View File

@@ -1,4 +1,3 @@
import logging
logging.info("Hello %s" % "World!")
logging.log(logging.INFO, "Hello %s" % "World!")

View File

@@ -1,4 +1,3 @@
import logging
logging.info("Hello" + " " + "World!")
logging.log(logging.INFO, "Hello" + " " + "World!")

View File

@@ -2,4 +2,3 @@ import logging
name = "world"
logging.info(f"Hello {name}")
logging.log(logging.INFO, f"Hello {name}")

View File

@@ -1,161 +1,79 @@
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] = [ # OK
x: list[str] = [
"foo",
"bar",
"baz",
]
) -> None: ...
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = ( # OK
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
)
) -> None: ...
) # OK
) -> None:
...
def f15(
x: set[str] = { # OK
x: set[str] = {
"foo",
"bar",
"baz",
}
) -> 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: ...
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" # OK
+ "bar",
) -> None: ...
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" # OK
+ b"bar",
) -> None: ...
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" # OK
+ 4,
) -> None: ...
x: object = "foo" + 4, # OK
) -> 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 # 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: ...
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -11,74 +11,32 @@ def f12(
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
def f13(
x: list[str] = [ # OK
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[str, ...] = ( # OK
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[str] = { # OK
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"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
@@ -151,11 +109,8 @@ 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: ...

View File

@@ -1,75 +0,0 @@
# 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

View File

@@ -1,59 +0,0 @@
# 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

View File

@@ -39,58 +39,6 @@ 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

View File

@@ -4,60 +4,26 @@ def f12(
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # OK
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # OK
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # OK
x={ # Error PYI014
"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
@@ -78,5 +44,5 @@ def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...
def f23(
x=True, # OK
x=True, # OK
) -> None: ...

View File

@@ -1,48 +0,0 @@
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

View File

@@ -1,51 +0,0 @@
import builtins
import typing
from typing import TypeAlias, Final
# 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
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

View File

@@ -2,13 +2,3 @@ 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

View File

@@ -20,5 +20,3 @@ 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

View File

@@ -1,10 +1,10 @@
if a or True: # SIM222
if a or True: # SIM223
pass
if (a or b) or True: # SIM222
if (a or b) or True: # SIM223
pass
if a or (b or True): # SIM222
if a or (b or True): # SIM223
pass
if a and True: # OK
@@ -16,16 +16,3 @@ 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

View File

@@ -12,15 +12,3 @@ 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: # SIM222
pass
if False and f() and a and g() and b: # SIM222
pass
if a and False and f() and b and g(): # SIM222
pass

View File

@@ -7,4 +7,3 @@ from ..protocol import commands, definitions, responses
from ..server import example
from .. import server
from . import logger, models
from ..protocol.UpperCaseModule import some_function

View File

@@ -2,9 +2,7 @@ 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

View File

@@ -1,8 +0,0 @@
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

View File

@@ -1,32 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(paste(\"Hello\",\"WoRld\"))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "R",
"language": "R",
"name": "ir"
},
"language_info": {
"codemirror_mode": "r",
"file_extension": ".r",
"mimetype": "text/x-r-source",
"name": "R",
"pygments_lexer": "r",
"version": "3.2.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# In[1]:
def unused_variable():
x = 1
y = 2
print(f"cell one: {y}")
unused_variable()
# Let's do another mistake
# In[2]:
def mutable_argument(z=set()):
print(f"cell two: {z}")
mutable_argument()

View File

@@ -1 +0,0 @@
broken "§=($/=(")

View File

@@ -1,88 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cell one: 2\n"
]
}
],
"source": [
"def unused_variable():\n",
" x = 1\n",
" y = 2\n",
" print(f\"cell one: {y}\")\n",
"\n",
"unused_variable()"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"start_time": "2023-03-08T23:01:09.705831Z",
"end_time": "2023-03-08T23:01:09.782916Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"Let's do another mistake"
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true,
"ExecuteTime": {
"start_time": "2023-03-08T23:01:09.733809Z",
"end_time": "2023-03-08T23:01:09.915760Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cell two: set()\n"
]
}
],
"source": [
"def mutable_argument(z=set()):\n",
" print(f\"cell two: {z}\")\n",
"\n",
"mutable_argument()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@@ -13,8 +13,3 @@ result = {
'key1': 'value',
'key2': 'value',
}
def foo() -> None:
#: E231
if (1,2):
pass

View File

@@ -56,29 +56,7 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
# OK
# https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
# OK
# Not 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))
"""

View File

@@ -1,15 +0,0 @@
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."""

View File

@@ -113,13 +113,3 @@ def f(x, *args, **kwargs):
**kwargs: keyword arguments
"""
return x
class Test:
def f(self, /, arg1: int) -> None:
"""
Some beauty description.
Args:
arg1: some description of arg
"""

View File

@@ -504,12 +504,3 @@ Testing this incorrectly indented docstring.
x: Test argument.
"""
def implicit_string_concatenation():
"""Toggle the gizmo.
Returns
A value of some sort.
""""Extra content"

View File

@@ -1,19 +1,10 @@
"""Test: imports within `ModuleNotFoundError` and `ImportError` handlers."""
"""Test: imports within `ModuleNotFoundError` handlers."""
def module_not_found_error():
def check_orjson():
try:
import orjson
return True
except ModuleNotFoundError:
return False
def import_error():
try:
import orjson
return True
except ImportError:
return False

View File

@@ -1,7 +0,0 @@
"""Test: parsing of nested string annotations."""
from typing import List
from pathlib import Path, PurePath
x: """List['Path']""" = []

View File

@@ -4,7 +4,6 @@
"{1:{0}}".format(1, 2) # No issues
"{1:{0}}".format(1, 2, 3) # F523
"{0}{2}".format(1, 2) # F523, # F524
"{1.arg[1]!r:0{2['arg']}{1}}".format(1, 2, 3, 4) # F523
# With no indexes
"{}".format(1, 2) # F523

View File

@@ -8,6 +8,3 @@ def f() -> "A":
def g() -> "///":
pass
X: """List[int]"""'' = []

View File

@@ -1,8 +0,0 @@
"""Test case: ForwardRef."""
from typing import ForwardRef, TypeVar
X = ForwardRef("List[int]")
Y: ForwardRef("List[int]")
Z = TypeVar("X", "List[int]", "int")

View File

@@ -1,7 +0,0 @@
"""Test case: f-strings in type annotations."""
from typing import List
x = 1
x: List[f"i{x}nt"] = []

View File

@@ -1,9 +0,0 @@
"""Test case: f-strings in future type annotations."""
from __future__ import annotations
from typing import List
x = 1
x: List[f"i{x}nt"] = []

View File

@@ -1,24 +0,0 @@
def test_division():
a = 9 / 3
assert "No ZeroDivisionError were raised" # [assert-on-string-literal]
def test_division():
a = 9 / 3
assert a == 3
try:
assert "bad" # [assert-on-string-literal]
except:
assert "bad again" # [assert-on-string-literal]
a = 12
assert f"hello {a}" # [assert-on-string-literal]
assert f"{a}" # [assert-on-string-literal]
assert f"" # [assert-on-string-literal]
assert "" # [assert-on-string-literal]
assert b"hello" # [assert-on-string-literal]
assert "", b"hi" # [assert-on-string-literal]
assert "WhyNotHere?", "HereIsOk" # [assert-on-string-literal]
assert 12, "ok here"

View File

@@ -1,14 +0,0 @@
try:
1 / 0
except ZeroDivisionError or ValueError as e: # [binary-op-exception]
pass
try:
raise ValueError
except ZeroDivisionError and ValueError as e: # [binary-op-exception]
print(e)
try:
pass
except (ValueError, Exception, IOError):
pass

View File

@@ -37,24 +37,3 @@ def print_python_version():
def print_python_version():
if 2 * 2 == 4:
return None
def print_python_version():
"""This function returns None."""
return None
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

View File

@@ -30,29 +30,8 @@ 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:
list = "abc"
def f(x: List[str]) -> None:
...

View File

@@ -46,8 +46,3 @@ def f(x: Union[("str", "int"), float]) -> None:
def f() -> None:
x: Optional[str]
x = Optional[str]
x = Union[str, int]
x = Union["str", "int"]
x: Union[str, int]
x: Union["str", "int"]

View File

@@ -1,9 +1,9 @@
import io
with io.open("f.txt", mode="r", buffering=-1, **kwargs) as f:
print(f.read())
from io import open
with open("f.txt") as f:
print(f.read())
import io
with io.open("f.txt", mode="r", buffering=-1, **kwargs) as f:
print(f.read())

View File

@@ -73,13 +73,3 @@ print("%s \N{snowman}" % (a,))
print("%(foo)s \N{snowman}" % {"foo": 1})
print(("foo %s " "bar %s") % (x, y))
# Single-value expressions
print('Hello %s' % "World")
print('Hello %s' % f"World")
print('Hello %s (%s)' % bar)
print('Hello %s (%s)' % bar.baz)
print('Hello %s (%s)' % bar['bop'])
print('Hello %(arg)s' % bar)
print('Hello %(arg)s' % bar.baz)
print('Hello %(arg)s' % bar['bop'])

View File

@@ -1,4 +1,6 @@
# OK
"%s" % unknown_type
b"%s" % (b"bytestring",)
"%*s" % (5, "hi")
@@ -55,9 +57,3 @@ pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
"""
% (x,)
)
'Hello %s' % bar
'Hello %s' % bar.baz
'Hello %s' % bar['bop']

View File

@@ -86,14 +86,3 @@ async def c():
async def c():
return "{}".format(1 + await 3)
def d(osname, version, release):
return"{}-{}.{}".format(osname, version, release)
def e():
yield"{}".format(1)
assert"{}".format(1)

View File

@@ -1,6 +1,5 @@
input = [1, 2, 3]
otherInput = [2, 3, 4]
foo = [1, 2, 3, 4]
# OK
zip(input, otherInput) # different inputs
@@ -9,8 +8,6 @@ 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:])
@@ -20,6 +17,3 @@ 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))

View File

@@ -2,24 +2,12 @@
# noqa # comment
print() # noqa
print() # noqa # comment
print() # noqa # comment
print() # noqa comment
print() # noqa comment
print(a) # noqa
print(a) # noqa # comment
print(a) # noqa # comment
print(a) # noqa comment
print(a) # noqa comment
# noqa: E501, F821
# noqa: E501, F821 # comment
print() # noqa: E501, F821
print() # noqa: E501, F821 # comment
print() # noqa: E501, F821 # comment
print() # noqa: E501, F821 comment
print() # noqa: E501, F821 comment
print(a) # noqa: E501, F821
print(a) # noqa: E501, F821 # comment
print(a) # noqa: E501, F821 # comment
print(a) # noqa: E501, F821 comment
print(a) # noqa: E501, F821 comment

View File

@@ -40,16 +40,8 @@ def noreturn():
logger.exception("process failed")
def good_return_with_side_effects():
def still_good():
try:
pass
return process()
except MyException:
logger.exception("process failed")
def good_noexcept():
try:
pass
return process()
finally:
logger.exception("process failed")

View File

@@ -9,30 +9,30 @@ Running from the repo root should pick up and enforce the appropriate settings f
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the --fix option.
7 potentially fixable with the --fix option.
```
Running from the project directory itself should exhibit the same behavior:
```console
∴ (cd crates/ruff/resources/test/project/ && cargo run -p ruff_cli -- check .)
examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
project/file.py:1:8: F401 [*] `os` imported but unused
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
project/file.py:1:8: F401 `os` imported but unused
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the --fix option.
7 potentially fixable with the --fix option.
```
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
@@ -40,10 +40,10 @@ files:
```console
∴ (cd crates/ruff/resources/test/project/examples/docs && cargo run -p ruff_cli -- check .)
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
Found 2 errors.
[*] 2 potentially fixable with the --fix option.
2 potentially fixable with the --fix option.
```
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
@@ -51,17 +51,17 @@ file paths from the current working directory:
```console
∴ (cargo run -p ruff_cli -- check --config=crates/ruff/resources/test/project/pyproject.toml crates/ruff/resources/test/project/)
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 [*] `docs.concepts.file` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused
Found 9 errors.
[*] 9 potentially fixable with the --fix option.
9 potentially fixable with the --fix option.
```
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
@@ -69,21 +69,21 @@ included in the output):
```console
∴ (cd crates/ruff/resources/test/project/examples && cargo run -p ruff_cli -- check --config=docs/ruff.toml .)
docs/docs/concepts/file.py:5:5: F841 [*] Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
Found 4 errors.
[*] 4 potentially fixable with the --fix option.
4 potentially fixable with the --fix option.
```
Passing an excluded directory directly should report errors in the contained files:
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
Found 1 error.
[*] 1 potentially fixable with the --fix option.
1 potentially fixable with the --fix option.
```
Unless we `--force-exclude`:

View File

@@ -6,7 +6,7 @@ use libcst_native::{
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::Edit;
use ruff_diagnostics::Fix;
use ruff_python_ast::helpers;
use ruff_python_ast::helpers::to_absolute;
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
@@ -178,7 +178,7 @@ pub fn delete_stmt(
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Edit> {
) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
.map_or(Ok(None), |v| v.map(Some))?
@@ -186,7 +186,7 @@ pub fn delete_stmt(
{
// If removing this node would lead to an invalid syntax tree, replace
// it with a `pass`.
Ok(Edit::replacement(
Ok(Fix::replacement(
"pass".to_string(),
stmt.location,
stmt.end_location.unwrap(),
@@ -194,22 +194,22 @@ pub fn delete_stmt(
} else {
Ok(if let Some(semicolon) = trailing_semicolon(stmt, locator) {
let next = next_stmt_break(semicolon, locator);
Edit::deletion(stmt.location, next)
Fix::deletion(stmt.location, next)
} else if helpers::match_leading_content(stmt, locator) {
Edit::deletion(stmt.location, stmt.end_location.unwrap())
Fix::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.
Edit::replacement(
Fix::replacement(
stylist.line_ending().to_string(),
stmt.location,
stmt.end_location.unwrap(),
)
} else {
Edit::deletion(stmt.location, stmt.end_location.unwrap())
Fix::deletion(stmt.location, stmt.end_location.unwrap())
}
} else {
Edit::deletion(
Fix::deletion(
Location::new(stmt.location.row(), 0),
Location::new(stmt.end_location.unwrap().row() + 1, 0),
)
@@ -226,7 +226,7 @@ pub fn remove_unused_imports<'a>(
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Edit> {
) -> Result<Fix> {
let module_text = locator.slice(stmt);
let mut tree = match_module(module_text)?;
@@ -327,13 +327,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(Edit::replacement(
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
@@ -355,7 +355,7 @@ pub fn remove_argument(
args: &[Expr],
keywords: &[Keyword],
remove_parentheses: bool,
) -> Result<Edit> {
) -> Result<Fix> {
// TODO(sbrugman): Preserve trailing comments.
let contents = locator.skip(stmt_at);
@@ -437,7 +437,7 @@ pub fn remove_argument(
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
(Some(start), Some(end)) => Ok(Fix::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}

View File

@@ -4,7 +4,7 @@ use itertools::Itertools;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
@@ -15,7 +15,7 @@ pub mod helpers;
/// 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_empty()) {
if diagnostics.iter().all(|check| check.fix.is_none()) {
None
} else {
Some(apply_fixes(diagnostics.iter(), locator))
@@ -28,61 +28,54 @@ fn apply_fixes<'a>(
locator: &'a Locator<'a>,
) -> (String, FixTable) {
let mut output = String::with_capacity(locator.len());
let mut last_pos: Option<Location> = None;
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
let mut last_pos: Location = Location::new(1, 0);
let mut applied: BTreeSet<&Fix> = BTreeSet::default();
let mut fixed = FxHashMap::default();
for (rule, fix) in diagnostics
.filter_map(|diagnostic| {
if diagnostic.fix.is_empty() {
None
} else {
Some((diagnostic.kind.rule(), &diagnostic.fix))
}
diagnostic
.fix
.as_ref()
.map(|fix| (diagnostic.kind.rule(), 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 fix.edits().iter().all(|edit| applied.contains(edit)) {
if applied.contains(&fix) {
*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| {
fix.location()
.map_or(false, |fix_location| last_pos >= fix_location)
}) {
if last_pos > fix.location {
continue;
}
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 all contents from `last_pos` to `fix.location`.
let slice = locator.slice(Range::new(last_pos, fix.location));
output.push_str(slice);
// Add the patch itself.
output.push_str(&edit.content);
// Track that the edit was applied.
last_pos = Some(edit.end_location);
applied.insert(edit);
}
// Add the patch itself.
output.push_str(&fix.content);
// Track that the fix was applied.
last_pos = fix.end_location;
applied.insert(fix);
*fixed.entry(rule).or_default() += 1;
}
// Add the remaining content.
let slice = locator.skip(last_pos.unwrap_or_default());
let slice = locator.skip(last_pos);
output.push_str(slice);
(output, fixed)
}
/// Apply a single fix.
pub(crate) fn apply_fix(fix: &Edit, locator: &Locator) -> String {
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`.
@@ -101,8 +94,8 @@ pub(crate) fn apply_fix(fix: &Edit, locator: &Locator) -> String {
/// 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,
@@ -116,20 +109,21 @@ mod tests {
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Edit;
use ruff_diagnostics::Fix;
use ruff_python_ast::source_code::Locator;
use crate::autofix::{apply_fix, apply_fixes};
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
use crate::rules::pycodestyle::rules::NoNewLineAtEndOfFile;
fn create_diagnostics(edit: impl IntoIterator<Item = Edit>) -> Vec<Diagnostic> {
edit.into_iter()
.map(|edit| Diagnostic {
fn create_diagnostics(fixes: impl IntoIterator<Item = Fix>) -> Vec<Diagnostic> {
fixes
.into_iter()
.map(|fix| Diagnostic {
// The choice of rule here is arbitrary.
kind: MissingNewlineAtEndOfFile.into(),
location: edit.location,
end_location: edit.end_location,
fix: edit.into(),
kind: NoNewLineAtEndOfFile.into(),
location: fix.location,
end_location: fix.end_location,
fix: Some(fix),
parent: None,
})
.collect()
@@ -153,7 +147,7 @@ class A(object):
"#
.trim(),
);
let diagnostics = create_diagnostics([Edit {
let diagnostics = create_diagnostics([Fix {
content: "Bar".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 14),
@@ -179,7 +173,7 @@ class A(object):
"#
.trim(),
);
let diagnostics = create_diagnostics([Edit {
let diagnostics = create_diagnostics([Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
@@ -200,21 +194,21 @@ class A:
fn apply_two_removals() {
let locator = Locator::new(
r#"
class A(object, object, object):
class A(object, object):
...
"#
.trim(),
);
let diagnostics = create_diagnostics([
Edit {
Fix {
content: String::new(),
location: Location::new(1, 8),
location: Location::new(1, 7),
end_location: Location::new(1, 16),
},
Edit {
Fix {
content: String::new(),
location: Location::new(1, 22),
end_location: Location::new(1, 30),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
},
]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
@@ -222,7 +216,7 @@ class A(object, object, object):
assert_eq!(
contents,
r#"
class A(object):
class A:
...
"#
.trim()
@@ -240,12 +234,12 @@ class A(object):
.trim(),
);
let diagnostics = create_diagnostics([
Edit {
Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
Edit {
Fix {
content: "ignored".to_string(),
location: Location::new(1, 9),
end_location: Location::new(1, 11),
@@ -273,7 +267,7 @@ class A(object):
.trim(),
);
let contents = apply_fix(
&Edit {
&Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,9 @@ pub fn check_imports(
// Extract all imports from the AST.
let tracker = {
let mut tracker = ImportTracker::new(locator, directives, path);
tracker.visit_body(python_ast);
for stmt in python_ast {
tracker.visit_stmt(stmt);
}
tracker
};
let blocks: Vec<&Block> = tracker.iter().collect();

View File

@@ -1,32 +1,42 @@
#![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, Fix};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Locator, Stylist};
use ruff_python_ast::types::Range;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::logical_lines::{
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
use crate::rules::pycodestyle::rules::{
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, LogicalLines, TokenFlags,
whitespace_before_parameters,
};
use crate::settings::{flags, Settings};
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
fn expand_indent(line: &str) -> usize {
let line = line.trim_end_matches(['\n', '\r']);
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();
}
let mut indent = 0;
for c in line.bytes() {
match c {
b'\t' => indent = (indent / 8) * 8 + 8,
b' ' => indent += 1,
_ => break,
for c in line.chars() {
if c == '\t' {
indent = (indent / 8) * 8 + 8;
} else if c == ' ' {
indent += 1;
} else {
break;
}
}
indent
}
@@ -39,140 +49,151 @@ pub fn check_logical_lines(
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
#[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 indent_char = stylist.indentation().as_char();
let mut prev_line = None;
let mut prev_indent_level = None;
let indent_char = stylist.indentation().as_char();
for line in iter_logical_lines(tokens, locator) {
if line.mapping.is_empty() {
continue;
}
for line in &LogicalLines::from_tokens(tokens, locator) {
if line.flags().contains(TokenFlags::OPERATOR) {
for (location, kind) in space_around_operator(&line) {
// 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);
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: Fix::empty(),
fix: None,
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 (location, kind) in extraneous_whitespace(&line) {
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);
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: Fix::empty(),
fix: None,
parent: None,
});
}
}
}
if line.flags().contains(TokenFlags::KEYWORD) {
for (location, kind) in whitespace_around_keywords(&line) {
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 settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: Fix::empty(),
fix: None,
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: Fix::empty(),
fix: None,
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: Fix::empty(),
fix: None,
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,
});
}
}
if line.flags().contains(TokenFlags::BRACKET) {
for diagnostic in whitespace_before_parameters(
&line.tokens(),
should_fix_whitespace_before_parameters,
) {
#[cfg(feature = "logical_lines")]
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
}
}
// 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;
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
let should_fix =
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
for (location, kind) in indentation(
#[cfg(not(feature = "logical_lines"))]
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(
&line,
prev_line.as_ref(),
indent_char,
@@ -180,18 +201,20 @@ 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: Fix::empty(),
fix: None,
parent: None,
});
}
}
if !line.is_comment_only() {
if !line.is_comment() {
prev_line = Some(line);
prev_indent_level = Some(indent_level);
}
@@ -204,9 +227,10 @@ 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#"
@@ -215,9 +239,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> = LogicalLines::from_tokens(&lxr, &locator)
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
.into_iter()
.map(|line| line.text_trimmed().to_string())
.map(|line| line.text)
.collect();
let expected = vec![
"x = 1".to_string(),
@@ -236,12 +260,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> = LogicalLines::from_tokens(&lxr, &locator)
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
.into_iter()
.map(|line| line.text_trimmed().to_string())
.map(|line| line.text)
.collect();
let expected = vec![
"x = [\n 1,\n 2,\n 3,\n]".to_string(),
"x = [1, 2, 3, ]".to_string(),
"y = 2".to_string(),
"z = x + 1".to_string(),
];
@@ -250,11 +274,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> = LogicalLines::from_tokens(&lxr, &locator)
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
.into_iter()
.map(|line| line.text_trimmed().to_string())
.map(|line| line.text)
.collect();
let expected = vec!["x = 'abc'".to_string()];
let expected = vec!["x = \"xxx\"".to_string()];
assert_eq!(actual, expected);
let contents = r#"
@@ -263,9 +287,9 @@ def f():
f()"#;
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
let locator = Locator::new(contents);
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
.into_iter()
.map(|line| line.text_trimmed().to_string())
.map(|line| line.text)
.collect();
let expected = vec!["def f():", "x = 1", "f()"];
assert_eq!(actual, expected);
@@ -278,17 +302,11 @@ def f():
f()"#;
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
let locator = Locator::new(contents);
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
.into_iter()
.map(|line| line.text_trimmed().to_string())
.map(|line| line.text)
.collect();
let expected = vec![
"def f():",
"\"\"\"Docstring goes here.\"\"\"",
"",
"x = 1",
"f()",
];
let expected = vec!["def f():", "\"xxxxxxxxxxxxxxxxxxxx\"", "", "x = 1", "f()"];
assert_eq!(actual, expected);
}
}

View File

@@ -1,8 +1,7 @@
pub mod ast;
pub mod filesystem;
pub mod imports;
#[cfg(feature = "logical_lines")]
pub(crate) mod logical_lines;
pub mod logical_lines;
pub mod noqa;
pub mod physical_lines;
pub mod tokens;

View File

@@ -1,15 +1,16 @@
//! `NoQA` enforcement and validation.
use log::warn;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::{Diagnostic, Edit};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::noqa;
use crate::noqa::{Directive, FileExemption};
use crate::noqa::{extract_file_exemption, Directive, Exemption};
use crate::registry::{AsRule, Rule};
use crate::rule_redirects::get_redirect_target;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
@@ -25,79 +26,92 @@ pub fn check_noqa(
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
let lines: Vec<&str> = contents.universal_newlines().collect();
// Whether the file is exempted from all checks.
let mut file_exempted = false;
// Identify any codes that are globally exempted (within the current file).
let exemption = noqa::file_exemption(&lines, commented_lines);
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
// 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();
// Extract all `noqa` directives.
if enforce_noqa {
for lineno in commented_lines {
// 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 {
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;
}
match &exemption {
FileExemption::All => {
// If the file is exempted, ignore all diagnostics.
// 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()) {
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()) {
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) => {
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::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);
@@ -129,26 +143,30 @@ pub fn check_noqa(
match directive {
Directive::All(leading_spaces, start_byte, end_byte, trailing_spaces) => {
if matches.is_empty() {
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA { codes: None },
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.set_fix(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
if start - leading_spaces == 0 && end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
}
diagnostics.push(diagnostic);
}
@@ -189,9 +207,8 @@ pub fn check_noqa(
&& unknown_codes.is_empty()
&& unmatched_codes.is_empty())
{
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA {
@@ -210,26 +227,32 @@ pub fn check_noqa(
.collect(),
}),
},
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.set_fix(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
if start - leading_spaces == 0 && end == lines[row].chars().count()
{
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
} else {
diagnostic.set_fix(Edit::replacement(
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
Location::new(row + 1, start),
Location::new(row + 1, end),
));
}
}
@@ -244,42 +267,3 @@ pub fn check_noqa(
ignored_diagnostics.sort_unstable();
ignored_diagnostics
}
/// Generate a [`Edit`] to delete a `noqa` directive.
fn delete_noqa(
row: usize,
line: &str,
leading_spaces: usize,
start_byte: usize,
end_byte: usize,
trailing_spaces: usize,
) -> Edit {
if start_byte - leading_spaces == 0 && end_byte == line.len() {
// Ex) `# noqa`
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();
Edit::deletion(
Location::new(row + 1, start_char - leading_spaces),
Location::new(row + 1, end_char + trailing_spaces),
)
} else if line[end_byte..].trim_start().starts_with('#') {
// 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();
Edit::deletion(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char + trailing_spaces),
)
} else {
// 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();
Edit::deletion(
Location::new(row + 1, start_char + 1 + 1),
Location::new(row + 1, end_char + trailing_spaces),
)
}
}

View File

@@ -12,8 +12,8 @@ use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
tab_indentation, trailing_whitespace,
doc_line_too_long, indentation_contains_tabs, line_too_long, mixed_spaces_and_tabs,
no_newline_at_end_of_file, trailing_whitespace,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
@@ -35,25 +35,25 @@ pub fn check_physical_lines(
let enforce_blanket_noqa = settings.rules.enabled(Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangLeadingWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNotFirstLine);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangMissingPython);
let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(Rule::BlankLineWithWhitespace);
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
settings.rules.enabled(Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(Rule::IndentationContainsTabs);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace =
autofix.into() && settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
autofix.into() && settings.rules.should_fix(Rule::ShebangWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -154,8 +154,8 @@ pub fn check_physical_lines(
}
}
if enforce_tab_indentation {
if let Some(diagnostic) = tab_indentation(index, line) {
if enforce_indentation_contains_tabs {
if let Some(diagnostic) = indentation_contains_tabs(index, line) {
diagnostics.push(diagnostic);
}
}
@@ -165,7 +165,7 @@ pub fn check_physical_lines(
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
autofix.into() && settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}
@@ -182,8 +182,6 @@ 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};
@@ -197,8 +195,7 @@ 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 tokens: Vec<_> = lex(line, Mode::Module).collect();
let stylist = Stylist::from_tokens(&tokens, &locator);
let stylist = Stylist::from_contents(line, &locator);
let check_with_max_line_length = |line_length: usize| {
check_physical_lines(
@@ -214,7 +211,7 @@ mod tests {
flags::Autofix::Enabled,
)
};
assert_eq!(check_with_max_line_length(8), vec![]);
assert_eq!(check_with_max_line_length(8), vec![]);
assert!(!check_with_max_line_length(6).is_empty());
assert!(check_with_max_line_length(7).is_empty());
}
}

View File

@@ -54,9 +54,9 @@ pub fn check_tokens(
]);
let enforce_trailing_comma = settings.rules.any_enabled(&[
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
Rule::TrailingCommaMissing,
Rule::TrailingCommaOnBareTupleProhibited,
Rule::TrailingCommaProhibited,
]);
let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub);

View File

@@ -108,20 +108,20 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W191") => Rule::TabIndentation,
(Pycodestyle, "W191") => Rule::IndentationContainsTabs,
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::MissingNewlineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineWithWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,
// pyflakes
(Pyflakes, "401") => Rule::UnusedImport,
(Pyflakes, "402") => Rule::ImportShadowedByLoopVar,
(Pyflakes, "403") => Rule::UndefinedLocalWithImportStar,
(Pyflakes, "403") => Rule::ImportStar,
(Pyflakes, "404") => Rule::LateFutureImport,
(Pyflakes, "405") => Rule::UndefinedLocalWithImportStarUsage,
(Pyflakes, "406") => Rule::UndefinedLocalWithNestedImportStarUsage,
(Pyflakes, "405") => Rule::ImportStarUsage,
(Pyflakes, "406") => Rule::ImportStarNotPermitted,
(Pyflakes, "407") => Rule::FutureFeatureNotDefined,
(Pyflakes, "501") => Rule::PercentFormatInvalidFormat,
(Pyflakes, "502") => Rule::PercentFormatExpectedMapping,
@@ -141,7 +141,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyflakes, "601") => Rule::MultiValueRepeatedKeyLiteral,
(Pyflakes, "602") => Rule::MultiValueRepeatedKeyVariable,
(Pyflakes, "621") => Rule::ExpressionsInStarAssignment,
(Pyflakes, "622") => Rule::MultipleStarredExpressions,
(Pyflakes, "622") => Rule::TwoStarredExpressions,
(Pyflakes, "631") => Rule::AssertTuple,
(Pyflakes, "632") => Rule::IsLiteral,
(Pyflakes, "633") => Rule::InvalidPrintSyntax,
@@ -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::LoadBeforeGlobalDeclaration,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
@@ -185,21 +185,19 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ManualFromImport,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::RepeatedIsinstanceCalls,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::SysExitAlias,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0129") => Rule::AssertOnStringLiteral,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "W0711") => Rule::BinaryOpException,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "W2901") => Rule::RedefinedLoopName,
@@ -215,7 +213,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Bugbear, "005") => Rule::StripWithMultiCharacters,
(Flake8Bugbear, "006") => Rule::MutableArgumentDefault,
(Flake8Bugbear, "007") => Rule::UnusedLoopControlVariable,
(Flake8Bugbear, "008") => Rule::FunctionCallInDefaultArgument,
(Flake8Bugbear, "008") => Rule::FunctionCallArgumentDefault,
(Flake8Bugbear, "009") => Rule::GetAttrWithConstant,
(Flake8Bugbear, "010") => Rule::SetAttrWithConstant,
(Flake8Bugbear, "011") => Rule::AssertFalse,
@@ -238,7 +236,6 @@ 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,
@@ -284,19 +281,14 @@ 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,
(Flake8ImplicitStrConcat, "003") => Rule::ExplicitStringConcatenation,
// flake8-print
(Flake8Print, "1") => Rule::Print,
(Flake8Print, "3") => Rule::PPrint,
(Flake8Print, "1") => Rule::PrintFound,
(Flake8Print, "3") => Rule::PPrintFound,
// flake8-quotes
(Flake8Quotes, "000") => Rule::BadQuotesInlineString,
@@ -310,7 +302,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Annotations, "003") => Rule::MissingTypeKwargs,
(Flake8Annotations, "101") => Rule::MissingTypeSelf,
(Flake8Annotations, "102") => Rule::MissingTypeCls,
(Flake8Annotations, "201") => Rule::MissingReturnTypeUndocumentedPublicFunction,
(Flake8Annotations, "201") => Rule::MissingReturnTypePublicFunction,
(Flake8Annotations, "202") => Rule::MissingReturnTypePrivateFunction,
(Flake8Annotations, "204") => Rule::MissingReturnTypeSpecialMethod,
(Flake8Annotations, "205") => Rule::MissingReturnTypeStaticMethod,
@@ -318,32 +310,33 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Annotations, "401") => Rule::AnyType,
// flake8-2020
(Flake82020, "101") => Rule::SysVersionSlice3,
(Flake82020, "102") => Rule::SysVersion2,
(Flake82020, "101") => Rule::SysVersionSlice3Referenced,
(Flake82020, "102") => Rule::SysVersion2Referenced,
(Flake82020, "103") => Rule::SysVersionCmpStr3,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3,
(Flake82020, "202") => Rule::SixPY3,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3Referenced,
(Flake82020, "202") => Rule::SixPY3Referenced,
(Flake82020, "203") => Rule::SysVersionInfo1CmpInt,
(Flake82020, "204") => Rule::SysVersionInfoMinorCmpInt,
(Flake82020, "301") => Rule::SysVersion0,
(Flake82020, "301") => Rule::SysVersion0Referenced,
(Flake82020, "302") => Rule::SysVersionCmpStr10,
(Flake82020, "303") => Rule::SysVersionSlice1,
(Flake82020, "303") => Rule::SysVersionSlice1Referenced,
// flake8-simplify
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
(Flake8Simplify, "102") => Rule::CollapsibleIf,
(Flake8Simplify, "103") => Rule::NeedlessBool,
(Flake8Simplify, "105") => Rule::SuppressibleException,
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
(Flake8Simplify, "108") => Rule::IfElseBlockInsteadOfIfExp,
(Flake8Simplify, "108") => Rule::UseTernaryOperator,
(Flake8Simplify, "109") => Rule::CompareWithTuple,
(Flake8Simplify, "110") => Rule::ReimplementedBuiltin,
(Flake8Simplify, "112") => Rule::UncapitalizedEnvironmentVariables,
// (Flake8Simplify, "111") => Rule::ReimplementedBuiltin,
(Flake8Simplify, "112") => Rule::UseCapitalEnvironmentVariables,
(Flake8Simplify, "114") => Rule::IfWithSameArms,
(Flake8Simplify, "115") => Rule::OpenFileWithContextHandler,
(Flake8Simplify, "116") => Rule::IfElseBlockInsteadOfDictLookup,
(Flake8Simplify, "116") => Rule::ManualDictLookup,
(Flake8Simplify, "117") => Rule::MultipleWithStatements,
(Flake8Simplify, "118") => Rule::InDictKeys,
(Flake8Simplify, "118") => Rule::KeyInDict,
(Flake8Simplify, "201") => Rule::NegateEqualOp,
(Flake8Simplify, "202") => Rule::NegateNotEqualOp,
(Flake8Simplify, "208") => Rule::DoubleNegation,
@@ -355,15 +348,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Simplify, "222") => Rule::ExprOrTrue,
(Flake8Simplify, "223") => Rule::ExprAndFalse,
(Flake8Simplify, "300") => Rule::YodaConditions,
(Flake8Simplify, "401") => Rule::IfElseBlockInsteadOfDictGet,
(Flake8Simplify, "401") => Rule::DictGetWithDefault,
// pyupgrade
(Pyupgrade, "001") => Rule::UselessMetaclassType,
(Pyupgrade, "003") => Rule::TypeOfPrimitive,
(Pyupgrade, "004") => Rule::UselessObjectInheritance,
(Pyupgrade, "005") => Rule::DeprecatedUnittestAlias,
(Pyupgrade, "006") => Rule::NonPEP585Annotation,
(Pyupgrade, "007") => Rule::NonPEP604Annotation,
(Pyupgrade, "006") => Rule::DeprecatedCollectionType,
(Pyupgrade, "007") => Rule::TypingUnion,
(Pyupgrade, "008") => Rule::SuperCallWithParameters,
(Pyupgrade, "009") => Rule::UTF8EncodingDeclaration,
(Pyupgrade, "010") => Rule::UnnecessaryFutureImport,
@@ -378,32 +371,32 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "020") => Rule::OpenAlias,
(Pyupgrade, "021") => Rule::ReplaceUniversalNewlines,
(Pyupgrade, "022") => Rule::ReplaceStdoutStderr,
(Pyupgrade, "023") => Rule::DeprecatedCElementTree,
(Pyupgrade, "023") => Rule::RewriteCElementTree,
(Pyupgrade, "024") => Rule::OSErrorAlias,
(Pyupgrade, "025") => Rule::UnicodeKindPrefix,
(Pyupgrade, "026") => Rule::DeprecatedMockImport,
(Pyupgrade, "027") => Rule::UnpackedListComprehension,
(Pyupgrade, "028") => Rule::YieldInForLoop,
(Pyupgrade, "025") => Rule::RewriteUnicodeLiteral,
(Pyupgrade, "026") => Rule::RewriteMockImport,
(Pyupgrade, "027") => Rule::RewriteListComprehension,
(Pyupgrade, "028") => Rule::RewriteYieldFrom,
(Pyupgrade, "029") => Rule::UnnecessaryBuiltinImport,
(Pyupgrade, "030") => Rule::FormatLiterals,
(Pyupgrade, "031") => Rule::PrintfStringFormatting,
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::LRUCacheWithMaxsizeNone,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::NonPEP604Isinstance,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
// pydocstyle
(Pydocstyle, "100") => Rule::UndocumentedPublicModule,
(Pydocstyle, "101") => Rule::UndocumentedPublicClass,
(Pydocstyle, "102") => Rule::UndocumentedPublicMethod,
(Pydocstyle, "103") => Rule::UndocumentedPublicFunction,
(Pydocstyle, "104") => Rule::UndocumentedPublicPackage,
(Pydocstyle, "105") => Rule::UndocumentedMagicMethod,
(Pydocstyle, "106") => Rule::UndocumentedPublicNestedClass,
(Pydocstyle, "107") => Rule::UndocumentedPublicInit,
(Pydocstyle, "100") => Rule::PublicModule,
(Pydocstyle, "101") => Rule::PublicClass,
(Pydocstyle, "102") => Rule::PublicMethod,
(Pydocstyle, "103") => Rule::PublicFunction,
(Pydocstyle, "104") => Rule::PublicPackage,
(Pydocstyle, "105") => Rule::MagicMethod,
(Pydocstyle, "106") => Rule::PublicNestedClass,
(Pydocstyle, "107") => Rule::PublicInit,
(Pydocstyle, "200") => Rule::FitsOnOneLine,
(Pydocstyle, "201") => Rule::NoBlankLineBeforeFunction,
(Pydocstyle, "202") => Rule::NoBlankLineAfterFunction,
@@ -411,11 +404,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pydocstyle, "204") => Rule::OneBlankLineAfterClass,
(Pydocstyle, "205") => Rule::BlankLineAfterSummary,
(Pydocstyle, "206") => Rule::IndentWithSpaces,
(Pydocstyle, "207") => Rule::UnderIndentation,
(Pydocstyle, "208") => Rule::OverIndentation,
(Pydocstyle, "207") => Rule::NoUnderIndentation,
(Pydocstyle, "208") => Rule::NoOverIndentation,
(Pydocstyle, "209") => Rule::NewLineAfterLastParagraph,
(Pydocstyle, "210") => Rule::SurroundingWhitespace,
(Pydocstyle, "211") => Rule::BlankLineBeforeClass,
(Pydocstyle, "210") => Rule::NoSurroundingWhitespace,
(Pydocstyle, "211") => Rule::NoBlankLineBeforeClass,
(Pydocstyle, "212") => Rule::MultiLineSummaryFirstLine,
(Pydocstyle, "213") => Rule::MultiLineSummarySecondLine,
(Pydocstyle, "214") => Rule::SectionNotOverIndented,
@@ -432,9 +425,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pydocstyle, "407") => Rule::DashedUnderlineAfterSection,
(Pydocstyle, "408") => Rule::SectionUnderlineAfterName,
(Pydocstyle, "409") => Rule::SectionUnderlineMatchesSectionLength,
(Pydocstyle, "410") => Rule::NoBlankLineAfterSection,
(Pydocstyle, "411") => Rule::NoBlankLineBeforeSection,
(Pydocstyle, "412") => Rule::BlankLinesBetweenHeaderAndContent,
(Pydocstyle, "410") => Rule::BlankLineAfterSection,
(Pydocstyle, "411") => Rule::BlankLineBeforeSection,
(Pydocstyle, "412") => Rule::NoBlankLinesBetweenHeaderAndContent,
(Pydocstyle, "413") => Rule::BlankLineAfterLastSection,
(Pydocstyle, "414") => Rule::EmptyDocstringSection,
(Pydocstyle, "415") => Rule::EndsInPunctuation,
@@ -476,37 +469,16 @@ 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,
@@ -537,24 +509,24 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Datetimez, "012") => Rule::CallDateFromtimestamp,
// pygrep-hooks
(PygrepHooks, "001") => Rule::Eval,
(PygrepHooks, "001") => Rule::NoEval,
(PygrepHooks, "002") => Rule::DeprecatedLogWarn,
(PygrepHooks, "003") => Rule::BlanketTypeIgnore,
(PygrepHooks, "004") => Rule::BlanketNOQA,
// pandas-vet
(PandasVet, "002") => Rule::PandasUseOfInplaceArgument,
(PandasVet, "003") => Rule::PandasUseOfDotIsNull,
(PandasVet, "004") => Rule::PandasUseOfDotNotNull,
(PandasVet, "007") => Rule::PandasUseOfDotIx,
(PandasVet, "008") => Rule::PandasUseOfDotAt,
(PandasVet, "009") => Rule::PandasUseOfDotIat,
(PandasVet, "010") => Rule::PandasUseOfDotPivotOrUnstack,
(PandasVet, "011") => Rule::PandasUseOfDotValues,
(PandasVet, "012") => Rule::PandasUseOfDotReadTable,
(PandasVet, "013") => Rule::PandasUseOfDotStack,
(PandasVet, "015") => Rule::PandasUseOfPdMerge,
(PandasVet, "901") => Rule::PandasDfVariableName,
(PandasVet, "002") => Rule::UseOfInplaceArgument,
(PandasVet, "003") => Rule::UseOfDotIsNull,
(PandasVet, "004") => Rule::UseOfDotNotNull,
(PandasVet, "007") => Rule::UseOfDotIx,
(PandasVet, "008") => Rule::UseOfDotAt,
(PandasVet, "009") => Rule::UseOfDotIat,
(PandasVet, "010") => Rule::UseOfDotPivotOrUnstack,
(PandasVet, "011") => Rule::UseOfDotValues,
(PandasVet, "012") => Rule::UseOfDotReadTable,
(PandasVet, "013") => Rule::UseOfDotStack,
(PandasVet, "015") => Rule::UseOfPdMerge,
(PandasVet, "901") => Rule::DfIsABadVariableName,
// flake8-errmsg
(Flake8ErrMsg, "101") => Rule::RawStringInException,
@@ -562,60 +534,58 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8ErrMsg, "103") => Rule::DotFormatInException,
// flake8-pyi
(Flake8Pyi, "001") => Rule::UnprefixedTypeParam,
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "006") => Rule::BadVersionInfoComparison,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(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, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::PytestFixtureIncorrectParenthesesStyle,
(Flake8PytestStyle, "002") => Rule::PytestFixturePositionalArgs,
(Flake8PytestStyle, "003") => Rule::PytestExtraneousScopeFunction,
(Flake8PytestStyle, "004") => Rule::PytestMissingFixtureNameUnderscore,
(Flake8PytestStyle, "005") => Rule::PytestIncorrectFixtureNameUnderscore,
(Flake8PytestStyle, "006") => Rule::PytestParametrizeNamesWrongType,
(Flake8PytestStyle, "007") => Rule::PytestParametrizeValuesWrongType,
(Flake8PytestStyle, "008") => Rule::PytestPatchWithLambda,
(Flake8PytestStyle, "009") => Rule::PytestUnittestAssertion,
(Flake8PytestStyle, "010") => Rule::PytestRaisesWithoutException,
(Flake8PytestStyle, "011") => Rule::PytestRaisesTooBroad,
(Flake8PytestStyle, "012") => Rule::PytestRaisesWithMultipleStatements,
(Flake8PytestStyle, "013") => Rule::PytestIncorrectPytestImport,
(Flake8PytestStyle, "015") => Rule::PytestAssertAlwaysFalse,
(Flake8PytestStyle, "016") => Rule::PytestFailWithoutMessage,
(Flake8PytestStyle, "017") => Rule::PytestAssertInExcept,
(Flake8PytestStyle, "018") => Rule::PytestCompositeAssertion,
(Flake8PytestStyle, "019") => Rule::PytestFixtureParamWithoutValue,
(Flake8PytestStyle, "020") => Rule::PytestDeprecatedYieldFixture,
(Flake8PytestStyle, "021") => Rule::PytestFixtureFinalizerCallback,
(Flake8PytestStyle, "022") => Rule::PytestUselessYieldFixture,
(Flake8PytestStyle, "023") => Rule::PytestIncorrectMarkParenthesesStyle,
(Flake8PytestStyle, "024") => Rule::PytestUnnecessaryAsyncioMarkOnFixture,
(Flake8PytestStyle, "025") => Rule::PytestErroneousUseFixturesOnFixture,
(Flake8PytestStyle, "026") => Rule::PytestUseFixturesWithoutParameters,
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
(Flake8PytestStyle, "002") => Rule::FixturePositionalArgs,
(Flake8PytestStyle, "003") => Rule::ExtraneousScopeFunction,
(Flake8PytestStyle, "004") => Rule::MissingFixtureNameUnderscore,
(Flake8PytestStyle, "005") => Rule::IncorrectFixtureNameUnderscore,
(Flake8PytestStyle, "006") => Rule::ParametrizeNamesWrongType,
(Flake8PytestStyle, "007") => Rule::ParametrizeValuesWrongType,
(Flake8PytestStyle, "008") => Rule::PatchWithLambda,
(Flake8PytestStyle, "009") => Rule::UnittestAssertion,
(Flake8PytestStyle, "010") => Rule::RaisesWithoutException,
(Flake8PytestStyle, "011") => Rule::RaisesTooBroad,
(Flake8PytestStyle, "012") => Rule::RaisesWithMultipleStatements,
(Flake8PytestStyle, "013") => Rule::IncorrectPytestImport,
(Flake8PytestStyle, "015") => Rule::AssertAlwaysFalse,
(Flake8PytestStyle, "016") => Rule::FailWithoutMessage,
(Flake8PytestStyle, "017") => Rule::AssertInExcept,
(Flake8PytestStyle, "018") => Rule::CompositeAssertion,
(Flake8PytestStyle, "019") => Rule::FixtureParamWithoutValue,
(Flake8PytestStyle, "020") => Rule::DeprecatedYieldFixture,
(Flake8PytestStyle, "021") => Rule::FixtureFinalizerCallback,
(Flake8PytestStyle, "022") => Rule::UselessYieldFixture,
(Flake8PytestStyle, "023") => Rule::IncorrectMarkParenthesesStyle,
(Flake8PytestStyle, "024") => Rule::UnnecessaryAsyncioMarkOnFixture,
(Flake8PytestStyle, "025") => Rule::ErroneousUseFixturesOnFixture,
(Flake8PytestStyle, "026") => Rule::UseFixturesWithoutParameters,
// flake8-pie
(Flake8Pie, "790") => Rule::UnnecessaryPass,
(Flake8Pie, "794") => Rule::DuplicateClassFieldDefinition,
(Flake8Pie, "796") => Rule::NonUniqueEnums,
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::ReimplementedListBuiltin,
(Flake8Pie, "810") => Rule::MultipleStartsEndsWith,
(Flake8Pie, "807") => Rule::PreferListBuiltin,
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
// flake8-commas
(Flake8Commas, "812") => Rule::MissingTrailingComma,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTuple,
(Flake8Commas, "819") => Rule::ProhibitedTrailingComma,
(Flake8Commas, "812") => Rule::TrailingCommaMissing,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTupleProhibited,
(Flake8Commas, "819") => Rule::TrailingCommaProhibited,
// flake8-no-pep420
(Flake8NoPep420, "001") => Rule::ImplicitNamespacePackage,
@@ -623,9 +593,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// flake8-executable
(Flake8Executable, "001") => Rule::ShebangNotExecutable,
(Flake8Executable, "002") => Rule::ShebangMissingExecutableFile,
(Flake8Executable, "003") => Rule::ShebangMissingPython,
(Flake8Executable, "004") => Rule::ShebangLeadingWhitespace,
(Flake8Executable, "005") => Rule::ShebangNotFirstLine,
(Flake8Executable, "003") => Rule::ShebangPython,
(Flake8Executable, "004") => Rule::ShebangWhitespace,
(Flake8Executable, "005") => Rule::ShebangNewline,
// flake8-type-checking
(Flake8TypeChecking, "001") => Rule::TypingOnlyFirstPartyImport,
@@ -637,7 +607,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// tryceratops
(Tryceratops, "002") => Rule::RaiseVanillaClass,
(Tryceratops, "003") => Rule::RaiseVanillaArgs,
(Tryceratops, "004") => Rule::TypeCheckWithoutTypeError,
(Tryceratops, "004") => Rule::PreferTypeError,
(Tryceratops, "200") => Rule::ReraiseNoCause,
(Tryceratops, "201") => Rule::VerboseRaise,
(Tryceratops, "300") => Rule::TryConsiderElse,
@@ -646,31 +616,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Tryceratops, "401") => Rule::VerboseLogMessage,
// flake8-use-pathlib
(Flake8UsePathlib, "100") => Rule::OsPathAbspath,
(Flake8UsePathlib, "101") => Rule::OsChmod,
(Flake8UsePathlib, "102") => Rule::OsMkdir,
(Flake8UsePathlib, "103") => Rule::OsMakedirs,
(Flake8UsePathlib, "104") => Rule::OsRename,
(Flake8UsePathlib, "100") => Rule::PathlibAbspath,
(Flake8UsePathlib, "101") => Rule::PathlibChmod,
(Flake8UsePathlib, "102") => Rule::PathlibMkdir,
(Flake8UsePathlib, "103") => Rule::PathlibMakedirs,
(Flake8UsePathlib, "104") => Rule::PathlibRename,
(Flake8UsePathlib, "105") => Rule::PathlibReplace,
(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,
(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,
// flake8-logging-format
(Flake8LoggingFormat, "001") => Rule::LoggingStringFormat,
@@ -696,19 +666,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "001") => Rule::AmbiguousUnicodeCharacterString,
(Ruff, "002") => Rule::AmbiguousUnicodeCharacterDocstring,
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "005") => Rule::CollectionLiteralConcatenation,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "007") => Rule::PairwiseOverZipped,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django
(Flake8Django, "001") => Rule::DjangoNullableModelStringField,
(Flake8Django, "003") => Rule::DjangoLocalsInRenderFunction,
(Flake8Django, "006") => Rule::DjangoExcludeWithModelForm,
(Flake8Django, "007") => Rule::DjangoAllWithModelForm,
(Flake8Django, "008") => Rule::DjangoModelWithoutDunderStr,
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
(Flake8Django, "001") => Rule::NullableModelStringField,
(Flake8Django, "003") => Rule::LocalsInRenderFunction,
(Flake8Django, "006") => Rule::ExcludeWithModelForm,
(Flake8Django, "007") => Rule::AllWithModelForm,
(Flake8Django, "008") => Rule::ModelWithoutDunderStr,
(Flake8Django, "013") => Rule::NonLeadingReceiverDecorator,
_ => return None,
})

View File

@@ -1,7 +1,6 @@
use anyhow::{bail, Result};
use libcst_native::{
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportFrom, Module, SimpleString,
SmallStatement, Statement,
Call, Comparison, Expr, Expression, Import, ImportFrom, Module, SmallStatement, Statement,
};
pub fn match_module(module_text: &str) -> Result<Module> {
@@ -71,31 +70,3 @@ pub fn match_comparison<'a, 'b>(
bail!("Expected Expression::Comparison")
}
}
pub fn match_dict<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Dict<'b>> {
if let Expression::Dict(dict) = expression {
Ok(dict)
} else {
bail!("Expected Expression::Dict")
}
}
pub fn match_attribute<'a, 'b>(
expression: &'a mut Expression<'b>,
) -> Result<&'a mut Attribute<'b>> {
if let Expression::Attribute(attribute) = expression {
Ok(attribute)
} else {
bail!("Expected Expression::Attribute")
}
}
pub fn match_simple_string<'a, 'b>(
expression: &'a mut Expression<'b>,
) -> Result<&'a mut SimpleString<'b>> {
if let Expression::SimpleString(simple_string) = expression {
Ok(simple_string)
} else {
bail!("Expected Expression::SimpleString")
}
}

View File

@@ -29,13 +29,12 @@ 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,

View File

@@ -2,18 +2,15 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use ruff_python_ast::visibility::{Modifier, VisibleScope};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use ruff_python_ast::visibility::{Modifier, VisibleScope};
/// Extract a docstring from a function or class body.
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
let stmt = suite.first()?;
// Require the docstring to be a standalone expression.
let StmtKind::Expr { value } = &stmt.node else {
return None;
};
// Only match strings.
if !matches!(
&value.node,
ExprKind::Constant {
@@ -28,10 +25,10 @@ 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: &VisibleScope,
stmt: &'a Stmt,
body: &'a [Stmt],
kind: Documentable,
kind: &Documentable,
) -> Definition<'a> {
let expr = docstring_from(body);
match kind {

View File

@@ -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

View File

@@ -2,7 +2,6 @@ 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,

17
crates/ruff/src/fix.rs Normal file
View File

@@ -0,0 +1,17 @@
#[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
}
}
}

Some files were not shown because too many files have changed in this diff Show More