Compare commits

...

55 Commits

Author SHA1 Message Date
Charlie Marsh
615887a7fe Bump version to v0.0.258 (#3671) 2023-03-22 15:02:57 -04:00
Charlie Marsh
07808a58f2 Refactor out common exemption-parsing logic (#3670) 2023-03-22 15:02:07 -04:00
Ran Benita
fe568c08d2 isort: fix bad interaction between force-sort-within-sections and force-to-top (#3645) 2023-03-22 14:00:00 -04:00
Charlie Marsh
7741d43ae5 Allow pairwise diagnostics for zip(..., strict=True) (#3669) 2023-03-22 13:03:43 -04:00
Charlie Marsh
1b3e54231c Flag, but don't fix, unused imports in ModuleNotFoundError blocks (#3658) 2023-03-22 13:03:30 -04:00
Charlie Marsh
3a8e98341b Enable autofix for annotations within 'simple' string literals (#3657) 2023-03-22 12:45:51 -04:00
kyoto7250
8593739f88 Check indentation level when executing E231 (#3668) 2023-03-22 12:32:00 -04:00
Charlie Marsh
242dd3dae1 Rename remaining use-* rules (#3661) 2023-03-22 11:36:01 -04:00
Charlie Marsh
875f61cb62 Rename pathlib rules to match updated naming convention (#3660) 2023-03-22 11:35:45 -04:00
Jonathan Plasse
3ec1ea8ac2 Add cargo-udeps in CI (#3646) 2023-03-22 15:53:12 +01:00
Charlie Marsh
1e45b13958 Remove linked issue from flake8-django (#3664) 2023-03-22 03:26:22 +00:00
Dhruv Manilawala
9e61956711 [flake8-django]: Implement rule DJ012 (#3659) 2023-03-22 03:07:58 +00:00
Jonathan Plasse
5eae3fbbfb Avoid RUF007 fixes for more than two arguments (#3654) 2023-03-21 22:17:31 +00:00
Colin Delahunty
41e38ffa98 [flake8-bandit]: Implement deny-list rules for suspicious member calls (#3239) 2023-03-21 15:11:52 -04:00
Charlie Marsh
27903cdb11 Replace logical_lines feature with debug_assertions (#3648) 2023-03-21 12:16:41 -04:00
Charlie Marsh
3b1709ba1e Avoid attempting infinite open fix with re-bound builtin (#3650) 2023-03-21 15:32:31 +00:00
Dhruv Manilawala
33394e4a69 docs: all flake8-comprehension rules (#3631) 2023-03-21 14:28:19 +00:00
Charlie Marsh
7b9bdc494a Consider same-site fixes to be overlapping (#3638) 2023-03-21 10:09:47 -04:00
James Greenhill
b06ca25421 Add PostHog to users of Ruff in README (#3641) 2023-03-21 10:05:35 -04:00
Jonathan Plasse
c42f8b93d2 Add Swatinem/rust-cache to benchmark-compare job (#3637) 2023-03-21 14:45:09 +01:00
Micha Reiser
f59a22b6e5 Remove unused dependencies (#3644) 2023-03-21 11:02:41 +01:00
Jonathan Plasse
b5edc6dfc9 Add autofix functionality for F523 (#3613) 2023-03-21 03:55:23 +00:00
Charlie Marsh
626169e2ef Avoid raising PEP 604 errors with forward-referenced members (#3640) 2023-03-20 23:49:41 -04:00
Charlie Marsh
e9f359ac5e Convert single-argument %-style format calls (#3600) 2023-03-21 03:35:10 +00:00
Jacob Latonis
318c2c80e2 pylint: Implement binary-op-exception (PLW0711) (#3639) 2023-03-21 03:33:40 +00:00
Jonathan Plasse
92aa3a8178 Use language: system for Rust hooks (#3616) 2023-03-20 22:44:21 -04:00
Jonathan Plasse
22a4ab51f9 Handle UP032 autofix with adjacent keywords (#3636) 2023-03-21 00:17:45 +00:00
Jonathan Plasse
f70a49ed8b Add autofix for magic methods (ANN204) (#3633) 2023-03-20 19:19:20 -04:00
Charlie Marsh
f039bf36a2 Avoid trimming escaped whitespace in D210 (#3635) 2023-03-20 17:17:42 -04:00
Jonathan Plasse
169dd72328 Fix TRY300 false positive (#3634) 2023-03-20 20:55:28 +00:00
Jonathan Plasse
fd39ec4bdd Merge Availability and AutofixKind (#3629) 2023-03-20 16:45:33 +00:00
Charlie Marsh
7c0f17279c Flag PEP 585 and PEP 604 violations in quoted annotations (#3593) 2023-03-20 11:15:44 -04:00
konstin
81d0884974 Add basic jupyter notebook support (#3440)
* Add basic jupyter notebook support behind a feature flag

* Address review comments

* Rename in separate commit to make both git and clippy happy

* cfg(feature = "jupyter_notebook") another test

* Address more review comments

* Address more review comments

* and clippy and windows

* More review comment
2023-03-20 12:06:01 +01:00
Jacob Latonis
a45753f462 [pylint]: Implement assert-on-string-literal (W0129) (#3610) 2023-03-19 23:45:51 -04:00
Zhengbo Wang
b08326162b Doc/CLN: pass pre-commit (#3604) 2023-03-19 19:20:11 +00:00
Dhruv Manilawala
3a65af4dae feat: update C416 with dict comprehension (autofixable) (#3605) 2023-03-19 18:37:28 +00:00
Ville Lindholm
474aa0b196 Fix infinite loop due to rules D207 & W605 (#3609) 2023-03-19 18:29:13 +00:00
Charlie Marsh
4892167217 Avoid panics for implicitly-concatenated docstrings (#3584)
## Summary

In the rare event that a docstring contains an implicit string concatenation, we currently have the potential to panic, because we assume that if a string starts with triple quotes, it _ends_ with triple quotes. But with implicit concatenation, that's not the case: a single `Expr` could start and end with different quote styles, because it can contain multiple string tokens.

Supporting these "properly" is pretty hard. In some cases it's hard to even know what the "right" behavior is. So for now, I'm just detecting and warning, which is better than a panic.

Closes #3543.

Closes #3585.
2023-03-19 14:16:50 -04:00
Micha Reiser
a5494b8541 Bitflag based RuleSet (#3606) 2023-03-19 17:09:06 +01:00
Micha Reiser
9ac9a1c69e Gracefully handle lint panics (#3509) 2023-03-19 17:08:38 +01:00
Rogdham
f06dff8af8 Change broken links in README to beta.ruff.rs (#3607) 2023-03-19 15:17:44 +00:00
Charlie Marsh
fe7443ce2f Use any_enabled in AST checker (#3601) 2023-03-19 10:44:33 -04:00
Henry Schreiner
4bdb2dd362 ci(check_ecosystem): add PyPa/build (#3569) 2023-03-18 19:09:22 -04:00
Henry Schreiner
53a4743631 ci: fix check_ecosystem (#3602) 2023-03-18 19:03:08 -04:00
Charlie Marsh
4ffcd8366a Rename a variety of rules to match updated conventions (#3283) 2023-03-18 17:35:59 -04:00
Charlie Marsh
dfb772c6f1 Avoid removing comment hash for noqa's with trailing content (#3589) 2023-03-18 18:48:52 +00:00
Jonathan Plasse
c21eb06922 Fix D417 false positive (#3596) 2023-03-18 13:14:03 -04:00
Charlie Marsh
16a350c731 Reduce usage of ALL in ecosystem CI (#3590) 2023-03-18 13:13:09 -04:00
Charlie Marsh
fa04861724 Check exclusions prior to resolving pyproject.toml files (#3588) 2023-03-18 13:12:49 -04:00
Micha Reiser
404504ab41 CI Checks: Fix malformed markdown (#3595)
The Benchmark results aren't formatted properly if the ecosystem check finds differences because the ecosystem check doesn't emit a trailing newline.

This PR adds the trailing newline to the ecosystem check script.
2023-03-18 10:04:50 +00:00
Charlie Marsh
621e4353e3 Re-add the list of supported plugins to the README (#3592) 2023-03-17 23:33:37 -04:00
Charlie Marsh
0c4926ff7b Bump version to v0.0.257 (#3591) 2023-03-17 22:34:10 -04:00
tomecki
61653b9f27 [pylint] Implement useless-return (R1711) (#3116) 2023-03-17 18:30:32 -04:00
Charlie Marsh
8dd3959e74 Update output in resources/test/project/README.md (#3587) 2023-03-17 21:51:03 +00:00
Charlie Marsh
50f9db21da Enable ANSI colors on Windows 10 (#3583) 2023-03-17 17:34:39 -04:00
429 changed files with 8981 additions and 3670 deletions

View File

@@ -26,7 +26,7 @@ jobs:
- name: "PR - Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: "PR - Build benchmarks"
uses: actions-rs/cargo@v1
@@ -75,59 +75,61 @@ jobs:
- run-benchmark
steps:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install Rust toolchain"
run: rustup show
- 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 cargo-binstall"
uses: taiki-e/install-action@cargo-binstall
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Install critcmp"
run: cargo binstall critcmp -y
- 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 | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./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: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./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 | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
echo ${{ github.event.pull_request.number }} > pr-number
- 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
cat summary.md > $GITHUB_STEP_SUMMARY
echo ${{ github.event.pull_request.number }} > pr-number
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md
- 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

View File

@@ -85,7 +85,6 @@ jobs:
name: ruff
path: target/debug/ruff
cargo-test-wasm:
runs-on: ubuntu-latest
name: "cargo test (wasm)"
@@ -176,3 +175,26 @@ 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

View File

@@ -65,6 +65,7 @@ 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: rust
language: system
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: rust
language: system
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: rust
language: system
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: rust
language: system
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 [README](https://github.com/charliermarsh/ruff#pyprojecttoml-discovery) for more.
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.

11
Cargo.lock generated
View File

@@ -780,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.256"
version = "0.0.258"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1982,7 +1982,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.256"
version = "0.0.258"
dependencies = [
"anyhow",
"bisection",
@@ -1990,7 +1990,6 @@ dependencies = [
"chrono",
"clap 4.1.8",
"colored",
"criterion",
"dirs",
"fern",
"glob",
@@ -2025,6 +2024,7 @@ dependencies = [
"schemars",
"semver",
"serde",
"serde_json",
"shellexpand",
"smallvec",
"strum",
@@ -2063,7 +2063,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.256"
version = "0.0.258"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2091,6 +2091,7 @@ dependencies = [
"ruff",
"ruff_cache",
"ruff_diagnostics",
"ruff_python_stdlib",
"rustc-hash",
"serde",
"serde_json",
@@ -2210,7 +2211,6 @@ name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
@@ -2505,6 +2505,7 @@ version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",

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" }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1", features = ["strum_macros"] }
@@ -48,8 +48,7 @@ textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
[profile.release]
panic = "abort"
lto = "thin"
lto = "fat"
codegen-units = 1
opt-level = 3

26
LICENSE
View File

@@ -393,7 +393,6 @@ are:
THE SOFTWARE.
"""
- autoflake, licensed as follows:
"""
Copyright (C) 2012-2018 Steven Myint
@@ -417,6 +416,31 @@ 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.256'
rev: 'v0.0.258'
hooks:
- id: ruff
```
@@ -227,6 +227,54 @@ 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-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/).
@@ -280,6 +328,7 @@ 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.256"
version = "0.0.258"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -85,8 +85,9 @@ 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 [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
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.)
## License

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.256"
version = "0.0.258"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -65,13 +65,13 @@ strum_macros = { workspace = true }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
toml = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
criterion = { version = "0.4.0" }
insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
[features]
default = []
logical_lines = []
jupyter_notebook = []

View File

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

View File

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

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

View File

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

View File

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

@@ -0,0 +1,113 @@
from django.db import models
from django.db.models import Model
class StrBeforeRandomField(models.Model):
"""Model with `__str__` before a random property."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return ""
random_property = "foo"
class StrBeforeFieldModel(models.Model):
"""Model with `__str__` before fields."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "foobar"
first_name = models.CharField(max_length=32)
class ManagerBeforeField(models.Model):
"""Model with manager before fields."""
objects = "manager"
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "foobar"
first_name = models.CharField(max_length=32)
class CustomMethodBeforeStr(models.Model):
"""Model with a custom method before `__str__`."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def my_method(self):
pass
def __str__(self):
return "foobar"
class GetAbsoluteUrlBeforeSave(Model):
"""Model with `get_absolute_url` method before `save` method.
Subclass this directly using the `Model` class.
"""
def get_absolute_url(self):
pass
def save(self):
pass
class ConstantsAreNotFields(models.Model):
"""Model with an assignment to a constant after `__str__`."""
first_name = models.CharField(max_length=32)
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
pass
MY_CONSTANT = id(1)
class PerfectlyFine(models.Model):
"""Model which has everything in perfect order."""
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
objects = "manager"
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "Perfectly fine!"
def save(self, **kwargs):
super(PerfectlyFine, self).save(**kwargs)
def get_absolute_url(self):
return "http://%s" % self
def my_method(self):
pass
@property
def random_property(self):
return "%s" % self

View File

@@ -2,7 +2,9 @@ from a import a1 # import_from
from c import * # import_from_star
import a # import
import c.d
from z import z1
import b as b1 # import_as
import z
from ..parent import *
from .my import fn

View File

@@ -0,0 +1,32 @@
{
"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

@@ -0,0 +1,24 @@
#!/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

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

View File

@@ -0,0 +1,88 @@
{
"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

@@ -0,0 +1 @@
{}

View File

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

View File

@@ -113,3 +113,13 @@ 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,3 +504,12 @@ 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,10 +1,19 @@
"""Test: imports within `ModuleNotFoundError` handlers."""
"""Test: imports within `ModuleNotFoundError` and `ImportError` handlers."""
def check_orjson():
def module_not_found_error():
try:
import orjson
return True
except ModuleNotFoundError:
return False
def import_error():
try:
import orjson
return True
except ImportError:
return False

View File

@@ -4,6 +4,7 @@
"{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

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

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

@@ -0,0 +1,50 @@
import sys
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
class SomeClass:
def print_python_version(self):
print(sys.version)
return None # [useless-return]
def print_python_version():
if 2 * 2 == 4:
return
print(sys.version)
def print_python_version():
if 2 * 2 == 4:
return None
return
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]

View File

@@ -30,8 +30,17 @@ def f(x: "List[str]") -> None:
...
list = "abc"
def f(x: List[str]) -> None:
def f(x: r"List[str]") -> None:
...
def f(x: "List[str]") -> None:
...
def f(x: """List[str]""") -> None:
...
def f(x: "Li" "st[str]") -> None:
...

View File

@@ -46,3 +46,8 @@ 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 @@
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())
from io import open
with open("f.txt") as f:
print(f.read())

View File

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

View File

@@ -86,3 +86,14 @@ 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,5 +1,6 @@
input = [1, 2, 3]
otherInput = [2, 3, 4]
foo = [1, 2, 3, 4]
# OK
zip(input, otherInput) # different inputs
@@ -8,6 +9,8 @@ zip(input, input[2:]) # not successive
zip(input[:-1], input[2:]) # not successive
list(zip(input, otherInput)) # nested call
zip(input, input[1::2]) # not successive
zip(foo[:-1], foo[1:], foo, strict=False) # more than 2 inputs
zip(foo[:-1], foo[1:], foo, strict=True) # more than 2 inputs
# Errors
zip(input, input[1:])
@@ -17,3 +20,6 @@ zip(input[1:], input[2:])
zip(input[1:-1], input[2:])
list(zip(input, input[1:]))
list(zip(input[:-1], input[1:]))
zip(foo[:-1], foo[1:], strict=True)
zip(foo[:-1], foo[1:], strict=False)
zip(foo[:-1], foo[1:], strict=bool(foo))

View File

@@ -2,12 +2,24 @@
# 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

@@ -45,3 +45,10 @@ def still_good():
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

@@ -28,7 +28,7 @@ fn apply_fixes<'a>(
locator: &'a Locator<'a>,
) -> (String, FixTable) {
let mut output = String::with_capacity(locator.len());
let mut last_pos: Location = Location::new(1, 0);
let mut last_pos: Option<Location> = None;
let mut applied: BTreeSet<&Fix> = BTreeSet::default();
let mut fixed = FxHashMap::default();
@@ -50,25 +50,25 @@ fn apply_fixes<'a>(
// Best-effort approach: if this fix overlaps with a fix we've already applied,
// skip it.
if last_pos > fix.location {
if last_pos.map_or(false, |last_pos| last_pos >= fix.location) {
continue;
}
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(Range::new(last_pos, fix.location));
let slice = locator.slice(Range::new(last_pos.unwrap_or_default(), fix.location));
output.push_str(slice);
// Add the patch itself.
output.push_str(&fix.content);
// Track that the fix was applied.
last_pos = fix.end_location;
last_pos = Some(fix.end_location);
applied.insert(fix);
*fixed.entry(rule).or_default() += 1;
}
// Add the remaining content.
let slice = locator.skip(last_pos);
let slice = locator.skip(last_pos.unwrap_or_default());
output.push_str(slice);
(output, fixed)
@@ -113,14 +113,14 @@ mod tests {
use ruff_python_ast::source_code::Locator;
use crate::autofix::{apply_fix, apply_fixes};
use crate::rules::pycodestyle::rules::NoNewLineAtEndOfFile;
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
fn create_diagnostics(fixes: impl IntoIterator<Item = Fix>) -> Vec<Diagnostic> {
fixes
.into_iter()
.map(|fix| Diagnostic {
// The choice of rule here is arbitrary.
kind: NoNewLineAtEndOfFile.into(),
kind: MissingNewlineAtEndOfFile.into(),
location: fix.location,
end_location: fix.end_location,
fix: Some(fix),
@@ -194,7 +194,7 @@ class A:
fn apply_two_removals() {
let locator = Locator::new(
r#"
class A(object, object):
class A(object, object, object):
...
"#
.trim(),
@@ -202,13 +202,13 @@ class A(object, object):
let diagnostics = create_diagnostics([
Fix {
content: String::new(),
location: Location::new(1, 7),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
},
Fix {
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
location: Location::new(1, 22),
end_location: Location::new(1, 30),
},
]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
@@ -216,7 +216,7 @@ class A(object, object):
assert_eq!(
contents,
r#"
class A:
class A(object):
...
"#
.trim()

File diff suppressed because it is too large Load Diff

View File

@@ -165,13 +165,15 @@ pub fn check_logical_lines(
}
}
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
#[cfg(not(feature = "logical_lines"))]
#[cfg(not(debug_assertions))]
let should_fix = false;
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
for diagnostic in
missing_whitespace(&line.text, start_loc.row(), should_fix, indent_level)
{
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
@@ -179,11 +181,11 @@ pub fn check_logical_lines(
}
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
let should_fix =
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
#[cfg(not(feature = "logical_lines"))]
#[cfg(not(debug_assertions))]
let should_fix = false;
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {

View File

@@ -1,6 +1,5 @@
//! `NoQA` enforcement and validation.
use log::warn;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
@@ -10,7 +9,7 @@ use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::noqa;
use crate::noqa::{extract_file_exemption, Directive, Exemption};
use crate::noqa::{Directive, FileExemption};
use crate::registry::{AsRule, Rule};
use crate::rule_redirects::get_redirect_target;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
@@ -26,63 +25,47 @@ pub fn check_noqa(
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
let mut file_exempted = false;
let lines: Vec<&str> = contents.universal_newlines().collect();
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
// Identify any codes that are globally exempted (within the current file).
let exemption = noqa::file_exemption(&lines, commented_lines);
// Map from line number to `noqa` directive on that line, along with any codes
// that were matched by the directive.
let mut noqa_directives: IntMap<usize, (Directive, Vec<NoqaCode>)> = IntMap::default();
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
file_exempted = true;
}
Exemption::Codes(codes) => {
file_exemptions.extend(codes.into_iter().filter_map(|code| {
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
Some(rule.noqa_code())
} else {
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
None
}
}));
}
Exemption::None => {}
}
if enforce_noqa {
// Extract all `noqa` directives.
if enforce_noqa {
for lineno in commented_lines {
noqa_directives
.entry(lineno - 1)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[lineno - 1]), vec![]));
}
}
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
// Remove any ignored diagnostics.
for (index, diagnostic) in diagnostics.iter().enumerate() {
if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) {
continue;
}
// If the file is exempted, ignore all diagnostics.
if file_exempted {
ignored_diagnostics.push(index);
continue;
}
// If the diagnostic is ignored by a global exemption, ignore it.
if !file_exemptions.is_empty() {
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
match &exemption {
FileExemption::All => {
// If the file is exempted, ignore all diagnostics.
ignored_diagnostics.push(index);
continue;
}
FileExemption::Codes(codes) => {
// If the diagnostic is ignored by a global exemption, ignore it.
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
ignored_diagnostics.push(index);
continue;
}
}
FileExemption::None => {}
}
// Is the violation ignored by a `noqa` directive on the parent line?
@@ -143,30 +126,26 @@ pub fn check_noqa(
match directive {
Directive::All(leading_spaces, start_byte, end_byte, trailing_spaces) => {
if matches.is_empty() {
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA { codes: None },
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
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),
));
}
diagnostic.amend(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
}
diagnostics.push(diagnostic);
}
@@ -207,8 +186,9 @@ pub fn check_noqa(
&& unknown_codes.is_empty()
&& unmatched_codes.is_empty())
{
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA {
@@ -227,32 +207,26 @@ pub fn check_noqa(
.collect(),
}),
},
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
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),
));
}
diagnostic.amend(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
} else {
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, end),
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
));
}
}
@@ -267,3 +241,42 @@ pub fn check_noqa(
ignored_diagnostics.sort_unstable();
ignored_diagnostics
}
/// Generate a [`Fix`] to delete a `noqa` directive.
fn delete_noqa(
row: usize,
line: &str,
leading_spaces: usize,
start_byte: usize,
end_byte: usize,
trailing_spaces: usize,
) -> Fix {
if start_byte - leading_spaces == 0 && end_byte == line.len() {
// Ex) `# noqa`
Fix::deletion(Location::new(row + 1, 0), Location::new(row + 2, 0))
} else if end_byte == line.len() {
// Ex) `x = 1 # noqa`
let start_char = line[..start_byte].chars().count();
let end_char = start_char + line[start_byte..end_byte].chars().count();
Fix::deletion(
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();
Fix::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();
Fix::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, indentation_contains_tabs, line_too_long, mixed_spaces_and_tabs,
no_newline_at_end_of_file, trailing_whitespace,
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
tab_indentation, 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::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangPython);
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_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::NoNewLineAtEndOfFile);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile);
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::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(Rule::IndentationContainsTabs);
settings.rules.enabled(Rule::BlankLineWithWhitespace);
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace =
autofix.into() && settings.rules.should_fix(Rule::ShebangWhitespace);
autofix.into() && settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
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_indentation_contains_tabs {
if let Some(diagnostic) = indentation_contains_tabs(index, line) {
if enforce_tab_indentation {
if let Some(diagnostic) = tab_indentation(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::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}

View File

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

View File

@@ -26,67 +26,67 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
Some(match (linter, code) {
// pycodestyle errors
(Pycodestyle, "E101") => Rule::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E111") => Rule::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E112") => Rule::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E113") => Rule::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E114") => Rule::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E115") => Rule::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E116") => Rule::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E117") => Rule::OverIndented,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E201") => Rule::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E202") => Rule::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E223") => Rule::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E225") => Rule::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E226") => Rule::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E227") => Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E228") => Rule::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E231") => Rule::MissingWhitespace,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E265") => Rule::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E266") => Rule::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E271") => Rule::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E272") => Rule::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E273") => Rule::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E274") => Rule::TabBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E275") => Rule::MissingWhitespaceAfterKeyword,
(Pycodestyle, "E401") => Rule::MultipleImportsOnOneLine,
(Pycodestyle, "E402") => Rule::ModuleImportNotAtTopOfFile,
@@ -108,20 +108,20 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W191") => Rule::IndentationContainsTabs,
(Pycodestyle, "W191") => Rule::TabIndentation,
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
(Pycodestyle, "W292") => Rule::MissingNewlineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineWithWhitespace,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,
// pyflakes
(Pyflakes, "401") => Rule::UnusedImport,
(Pyflakes, "402") => Rule::ImportShadowedByLoopVar,
(Pyflakes, "403") => Rule::ImportStar,
(Pyflakes, "403") => Rule::UndefinedLocalWithImportStar,
(Pyflakes, "404") => Rule::LateFutureImport,
(Pyflakes, "405") => Rule::ImportStarUsage,
(Pyflakes, "406") => Rule::ImportStarNotPermitted,
(Pyflakes, "405") => Rule::UndefinedLocalWithImportStarUsage,
(Pyflakes, "406") => Rule::UndefinedLocalWithNestedImportStarUsage,
(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::TwoStarredExpressions,
(Pyflakes, "622") => Rule::MultipleStarredExpressions,
(Pyflakes, "631") => Rule::AssertTuple,
(Pyflakes, "632") => Rule::IsLiteral,
(Pyflakes, "633") => Rule::InvalidPrintSyntax,
@@ -168,36 +168,39 @@ 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::UsedPriorGlobalDeclaration,
(Pylint, "E0118") => Rule::LoadBeforeGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
(Pylint, "E1307") => Rule::BadStringFormatType,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E2510") => Rule::InvalidCharacterBackspace,
(Pylint, "E2512") => Rule::InvalidCharacterSub,
(Pylint, "E2513") => Rule::InvalidCharacterEsc,
(Pylint, "E2514") => Rule::InvalidCharacterNul,
(Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0402") => Rule::ManualFromImport,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R1701") => Rule::RepeatedIsinstanceCalls,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::SysExitAlias,
(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,
// flake8-builtins
@@ -212,7 +215,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::FunctionCallArgumentDefault,
(Flake8Bugbear, "008") => Rule::FunctionCallInDefaultArgument,
(Flake8Bugbear, "009") => Rule::GetAttrWithConstant,
(Flake8Bugbear, "010") => Rule::SetAttrWithConstant,
(Flake8Bugbear, "011") => Rule::AssertFalse,
@@ -286,8 +289,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8ImplicitStrConcat, "003") => Rule::ExplicitStringConcatenation,
// flake8-print
(Flake8Print, "1") => Rule::PrintFound,
(Flake8Print, "3") => Rule::PPrintFound,
(Flake8Print, "1") => Rule::Print,
(Flake8Print, "3") => Rule::PPrint,
// flake8-quotes
(Flake8Quotes, "000") => Rule::BadQuotesInlineString,
@@ -301,7 +304,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::MissingReturnTypePublicFunction,
(Flake8Annotations, "201") => Rule::MissingReturnTypeUndocumentedPublicFunction,
(Flake8Annotations, "202") => Rule::MissingReturnTypePrivateFunction,
(Flake8Annotations, "204") => Rule::MissingReturnTypeSpecialMethod,
(Flake8Annotations, "205") => Rule::MissingReturnTypeStaticMethod,
@@ -309,33 +312,32 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Annotations, "401") => Rule::AnyType,
// flake8-2020
(Flake82020, "101") => Rule::SysVersionSlice3Referenced,
(Flake82020, "102") => Rule::SysVersion2Referenced,
(Flake82020, "101") => Rule::SysVersionSlice3,
(Flake82020, "102") => Rule::SysVersion2,
(Flake82020, "103") => Rule::SysVersionCmpStr3,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3Referenced,
(Flake82020, "202") => Rule::SixPY3Referenced,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3,
(Flake82020, "202") => Rule::SixPY3,
(Flake82020, "203") => Rule::SysVersionInfo1CmpInt,
(Flake82020, "204") => Rule::SysVersionInfoMinorCmpInt,
(Flake82020, "301") => Rule::SysVersion0Referenced,
(Flake82020, "301") => Rule::SysVersion0,
(Flake82020, "302") => Rule::SysVersionCmpStr10,
(Flake82020, "303") => Rule::SysVersionSlice1Referenced,
(Flake82020, "303") => Rule::SysVersionSlice1,
// flake8-simplify
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
(Flake8Simplify, "102") => Rule::CollapsibleIf,
(Flake8Simplify, "103") => Rule::NeedlessBool,
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
(Flake8Simplify, "105") => Rule::SuppressibleException,
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
(Flake8Simplify, "108") => Rule::UseTernaryOperator,
(Flake8Simplify, "108") => Rule::IfElseBlockInsteadOfIfExp,
(Flake8Simplify, "109") => Rule::CompareWithTuple,
(Flake8Simplify, "110") => Rule::ReimplementedBuiltin,
// (Flake8Simplify, "111") => Rule::ReimplementedBuiltin,
(Flake8Simplify, "112") => Rule::UseCapitalEnvironmentVariables,
(Flake8Simplify, "112") => Rule::UncapitalizedEnvironmentVariables,
(Flake8Simplify, "114") => Rule::IfWithSameArms,
(Flake8Simplify, "115") => Rule::OpenFileWithContextHandler,
(Flake8Simplify, "116") => Rule::ManualDictLookup,
(Flake8Simplify, "116") => Rule::IfElseBlockInsteadOfDictLookup,
(Flake8Simplify, "117") => Rule::MultipleWithStatements,
(Flake8Simplify, "118") => Rule::KeyInDict,
(Flake8Simplify, "118") => Rule::InDictKeys,
(Flake8Simplify, "201") => Rule::NegateEqualOp,
(Flake8Simplify, "202") => Rule::NegateNotEqualOp,
(Flake8Simplify, "208") => Rule::DoubleNegation,
@@ -347,15 +349,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::DictGetWithDefault,
(Flake8Simplify, "401") => Rule::IfElseBlockInsteadOfDictGet,
// pyupgrade
(Pyupgrade, "001") => Rule::UselessMetaclassType,
(Pyupgrade, "003") => Rule::TypeOfPrimitive,
(Pyupgrade, "004") => Rule::UselessObjectInheritance,
(Pyupgrade, "005") => Rule::DeprecatedUnittestAlias,
(Pyupgrade, "006") => Rule::DeprecatedCollectionType,
(Pyupgrade, "007") => Rule::TypingUnion,
(Pyupgrade, "006") => Rule::NonPEP585Annotation,
(Pyupgrade, "007") => Rule::NonPEP604Annotation,
(Pyupgrade, "008") => Rule::SuperCallWithParameters,
(Pyupgrade, "009") => Rule::UTF8EncodingDeclaration,
(Pyupgrade, "010") => Rule::UnnecessaryFutureImport,
@@ -370,32 +372,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::RewriteCElementTree,
(Pyupgrade, "023") => Rule::DeprecatedCElementTree,
(Pyupgrade, "024") => Rule::OSErrorAlias,
(Pyupgrade, "025") => Rule::RewriteUnicodeLiteral,
(Pyupgrade, "026") => Rule::RewriteMockImport,
(Pyupgrade, "027") => Rule::RewriteListComprehension,
(Pyupgrade, "028") => Rule::RewriteYieldFrom,
(Pyupgrade, "025") => Rule::UnicodeKindPrefix,
(Pyupgrade, "026") => Rule::DeprecatedMockImport,
(Pyupgrade, "027") => Rule::UnpackedListComprehension,
(Pyupgrade, "028") => Rule::YieldInForLoop,
(Pyupgrade, "029") => Rule::UnnecessaryBuiltinImport,
(Pyupgrade, "030") => Rule::FormatLiterals,
(Pyupgrade, "031") => Rule::PrintfStringFormatting,
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "033") => Rule::LRUCacheWithMaxsizeNone,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
(Pyupgrade, "038") => Rule::NonPEP604Isinstance,
// pydocstyle
(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, "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, "200") => Rule::FitsOnOneLine,
(Pydocstyle, "201") => Rule::NoBlankLineBeforeFunction,
(Pydocstyle, "202") => Rule::NoBlankLineAfterFunction,
@@ -403,11 +405,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::NoUnderIndentation,
(Pydocstyle, "208") => Rule::NoOverIndentation,
(Pydocstyle, "207") => Rule::UnderIndentation,
(Pydocstyle, "208") => Rule::OverIndentation,
(Pydocstyle, "209") => Rule::NewLineAfterLastParagraph,
(Pydocstyle, "210") => Rule::NoSurroundingWhitespace,
(Pydocstyle, "211") => Rule::NoBlankLineBeforeClass,
(Pydocstyle, "210") => Rule::SurroundingWhitespace,
(Pydocstyle, "211") => Rule::BlankLineBeforeClass,
(Pydocstyle, "212") => Rule::MultiLineSummaryFirstLine,
(Pydocstyle, "213") => Rule::MultiLineSummarySecondLine,
(Pydocstyle, "214") => Rule::SectionNotOverIndented,
@@ -424,9 +426,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::BlankLineAfterSection,
(Pydocstyle, "411") => Rule::BlankLineBeforeSection,
(Pydocstyle, "412") => Rule::NoBlankLinesBetweenHeaderAndContent,
(Pydocstyle, "410") => Rule::NoBlankLineAfterSection,
(Pydocstyle, "411") => Rule::NoBlankLineBeforeSection,
(Pydocstyle, "412") => Rule::BlankLinesBetweenHeaderAndContent,
(Pydocstyle, "413") => Rule::BlankLineAfterLastSection,
(Pydocstyle, "414") => Rule::EmptyDocstringSection,
(Pydocstyle, "415") => Rule::EndsInPunctuation,
@@ -468,16 +470,37 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Bandit, "105") => Rule::HardcodedPasswordString,
(Flake8Bandit, "106") => Rule::HardcodedPasswordFuncArg,
(Flake8Bandit, "107") => Rule::HardcodedPasswordDefault,
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
(Flake8Bandit, "108") => Rule::HardcodedTempFile,
(Flake8Bandit, "110") => Rule::TryExceptPass,
(Flake8Bandit, "112") => Rule::TryExceptContinue,
(Flake8Bandit, "113") => Rule::RequestWithoutTimeout,
(Flake8Bandit, "301") => Rule::SuspiciousPickleUsage,
(Flake8Bandit, "302") => Rule::SuspiciousMarshalUsage,
(Flake8Bandit, "303") => Rule::SuspiciousInsecureHashUsage,
(Flake8Bandit, "304") => Rule::SuspiciousInsecureCipherUsage,
(Flake8Bandit, "305") => Rule::SuspiciousInsecureCipherModeUsage,
(Flake8Bandit, "306") => Rule::SuspiciousMktempUsage,
(Flake8Bandit, "307") => Rule::SuspiciousEvalUsage,
(Flake8Bandit, "308") => Rule::SuspiciousMarkSafeUsage,
(Flake8Bandit, "310") => Rule::SuspiciousURLOpenUsage,
(Flake8Bandit, "311") => Rule::SuspiciousNonCryptographicRandomUsage,
(Flake8Bandit, "312") => Rule::SuspiciousTelnetUsage,
(Flake8Bandit, "313") => Rule::SuspiciousXMLCElementTreeUsage,
(Flake8Bandit, "314") => Rule::SuspiciousXMLElementTreeUsage,
(Flake8Bandit, "315") => Rule::SuspiciousXMLExpatReaderUsage,
(Flake8Bandit, "316") => Rule::SuspiciousXMLExpatBuilderUsage,
(Flake8Bandit, "317") => Rule::SuspiciousXMLSaxUsage,
(Flake8Bandit, "318") => Rule::SuspiciousXMLMiniDOMUsage,
(Flake8Bandit, "319") => Rule::SuspiciousXMLPullDOMUsage,
(Flake8Bandit, "320") => Rule::SuspiciousXMLETreeUsage,
(Flake8Bandit, "321") => Rule::SuspiciousFTPLibUsage,
(Flake8Bandit, "323") => Rule::SuspiciousUnverifiedContextUsage,
(Flake8Bandit, "324") => Rule::HashlibInsecureHashFunction,
(Flake8Bandit, "501") => Rule::RequestWithNoCertValidation,
(Flake8Bandit, "506") => Rule::UnsafeYAMLLoad,
(Flake8Bandit, "508") => Rule::SnmpInsecureVersion,
(Flake8Bandit, "509") => Rule::SnmpWeakCryptography,
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
(Flake8Bandit, "612") => Rule::LoggingConfigInsecureListen,
(Flake8Bandit, "701") => Rule::Jinja2AutoescapeFalse,
@@ -508,24 +531,24 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Datetimez, "012") => Rule::CallDateFromtimestamp,
// pygrep-hooks
(PygrepHooks, "001") => Rule::NoEval,
(PygrepHooks, "001") => Rule::Eval,
(PygrepHooks, "002") => Rule::DeprecatedLogWarn,
(PygrepHooks, "003") => Rule::BlanketTypeIgnore,
(PygrepHooks, "004") => Rule::BlanketNOQA,
// pandas-vet
(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,
(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,
// flake8-errmsg
(Flake8ErrMsg, "101") => Rule::RawStringInException,
@@ -533,58 +556,58 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8ErrMsg, "103") => Rule::DotFormatInException,
// flake8-pyi
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "001") => Rule::UnprefixedTypeParam,
(Flake8Pyi, "006") => Rule::BadVersionInfoComparison,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "011") => Rule::TypedArgumentDefaultInStub,
(Flake8Pyi, "014") => Rule::ArgumentDefaultInStub,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
// flake8-pytest-style
(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,
(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,
// flake8-pie
(Flake8Pie, "790") => Rule::UnnecessaryPass,
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
(Flake8Pie, "794") => Rule::DuplicateClassFieldDefinition,
(Flake8Pie, "796") => Rule::NonUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::PreferListBuiltin,
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
(Flake8Pie, "807") => Rule::ReimplementedListBuiltin,
(Flake8Pie, "810") => Rule::MultipleStartsEndsWith,
// flake8-commas
(Flake8Commas, "812") => Rule::TrailingCommaMissing,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTupleProhibited,
(Flake8Commas, "819") => Rule::TrailingCommaProhibited,
(Flake8Commas, "812") => Rule::MissingTrailingComma,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTuple,
(Flake8Commas, "819") => Rule::ProhibitedTrailingComma,
// flake8-no-pep420
(Flake8NoPep420, "001") => Rule::ImplicitNamespacePackage,
@@ -592,9 +615,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::ShebangPython,
(Flake8Executable, "004") => Rule::ShebangWhitespace,
(Flake8Executable, "005") => Rule::ShebangNewline,
(Flake8Executable, "003") => Rule::ShebangMissingPython,
(Flake8Executable, "004") => Rule::ShebangLeadingWhitespace,
(Flake8Executable, "005") => Rule::ShebangNotFirstLine,
// flake8-type-checking
(Flake8TypeChecking, "001") => Rule::TypingOnlyFirstPartyImport,
@@ -606,7 +629,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// tryceratops
(Tryceratops, "002") => Rule::RaiseVanillaClass,
(Tryceratops, "003") => Rule::RaiseVanillaArgs,
(Tryceratops, "004") => Rule::PreferTypeError,
(Tryceratops, "004") => Rule::TypeCheckWithoutTypeError,
(Tryceratops, "200") => Rule::ReraiseNoCause,
(Tryceratops, "201") => Rule::VerboseRaise,
(Tryceratops, "300") => Rule::TryConsiderElse,
@@ -615,31 +638,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Tryceratops, "401") => Rule::VerboseLogMessage,
// flake8-use-pathlib
(Flake8UsePathlib, "100") => Rule::PathlibAbspath,
(Flake8UsePathlib, "101") => Rule::PathlibChmod,
(Flake8UsePathlib, "102") => Rule::PathlibMkdir,
(Flake8UsePathlib, "103") => Rule::PathlibMakedirs,
(Flake8UsePathlib, "104") => Rule::PathlibRename,
(Flake8UsePathlib, "100") => Rule::OsPathAbspath,
(Flake8UsePathlib, "101") => Rule::OsChmod,
(Flake8UsePathlib, "102") => Rule::OsMkdir,
(Flake8UsePathlib, "103") => Rule::OsMakedirs,
(Flake8UsePathlib, "104") => Rule::OsRename,
(Flake8UsePathlib, "105") => Rule::PathlibReplace,
(Flake8UsePathlib, "106") => Rule::PathlibRmdir,
(Flake8UsePathlib, "107") => Rule::PathlibRemove,
(Flake8UsePathlib, "108") => Rule::PathlibUnlink,
(Flake8UsePathlib, "109") => Rule::PathlibGetcwd,
(Flake8UsePathlib, "110") => Rule::PathlibExists,
(Flake8UsePathlib, "111") => Rule::PathlibExpanduser,
(Flake8UsePathlib, "112") => Rule::PathlibIsDir,
(Flake8UsePathlib, "113") => Rule::PathlibIsFile,
(Flake8UsePathlib, "114") => Rule::PathlibIsLink,
(Flake8UsePathlib, "115") => Rule::PathlibReadlink,
(Flake8UsePathlib, "116") => Rule::PathlibStat,
(Flake8UsePathlib, "117") => Rule::PathlibIsAbs,
(Flake8UsePathlib, "118") => Rule::PathlibJoin,
(Flake8UsePathlib, "119") => Rule::PathlibBasename,
(Flake8UsePathlib, "120") => Rule::PathlibDirname,
(Flake8UsePathlib, "121") => Rule::PathlibSamefile,
(Flake8UsePathlib, "122") => Rule::PathlibSplitext,
(Flake8UsePathlib, "123") => Rule::PathlibOpen,
(Flake8UsePathlib, "124") => Rule::PathlibPyPath,
(Flake8UsePathlib, "106") => Rule::OsRmdir,
(Flake8UsePathlib, "107") => Rule::OsRemove,
(Flake8UsePathlib, "108") => Rule::OsUnlink,
(Flake8UsePathlib, "109") => Rule::OsGetcwd,
(Flake8UsePathlib, "110") => Rule::OsPathExists,
(Flake8UsePathlib, "111") => Rule::OsPathExpanduser,
(Flake8UsePathlib, "112") => Rule::OsPathIsdir,
(Flake8UsePathlib, "113") => Rule::OsPathIsfile,
(Flake8UsePathlib, "114") => Rule::OsPathIslink,
(Flake8UsePathlib, "115") => Rule::OsReadlink,
(Flake8UsePathlib, "116") => Rule::OsStat,
(Flake8UsePathlib, "117") => Rule::OsPathIsabs,
(Flake8UsePathlib, "118") => Rule::OsPathJoin,
(Flake8UsePathlib, "119") => Rule::OsPathBasename,
(Flake8UsePathlib, "120") => Rule::OsPathDirname,
(Flake8UsePathlib, "121") => Rule::OsPathSamefile,
(Flake8UsePathlib, "122") => Rule::OsPathSplitext,
(Flake8UsePathlib, "123") => Rule::BuiltinOpen,
(Flake8UsePathlib, "124") => Rule::PyPath,
// flake8-logging-format
(Flake8LoggingFormat, "001") => Rule::LoggingStringFormat,
@@ -665,18 +688,19 @@ 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::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "005") => Rule::CollectionLiteralConcatenation,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "007") => Rule::PairwiseOverZipped,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django
(Flake8Django, "001") => Rule::NullableModelStringField,
(Flake8Django, "003") => Rule::LocalsInRenderFunction,
(Flake8Django, "006") => Rule::ExcludeWithModelForm,
(Flake8Django, "007") => Rule::AllWithModelForm,
(Flake8Django, "008") => Rule::ModelWithoutDunderStr,
(Flake8Django, "013") => Rule::NonLeadingReceiverDecorator,
(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,
_ => return None,
})

View File

@@ -1,6 +1,7 @@
use anyhow::{bail, Result};
use libcst_native::{
Call, Comparison, Expr, Expression, Import, ImportFrom, Module, SmallStatement, Statement,
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportFrom, Module, SimpleString,
SmallStatement, Statement,
};
pub fn match_module(module_text: &str) -> Result<Module> {
@@ -70,3 +71,31 @@ 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

@@ -2,15 +2,18 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use ruff_python_ast::visibility::{Modifier, VisibleScope};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
/// 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 {

View File

@@ -4,9 +4,8 @@ use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::Rule;
use crate::registry::RuleSet;
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -24,35 +23,34 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path(
path: &Path,
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
) -> FxHashSet<Rule> {
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, RuleSet)],
) -> RuleSet {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs
.iter()
.filter_map(|(absolute, basename, codes)| {
.filter_map(|(absolute, basename, rules)| {
if basename.is_match(file_basename) {
debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
basename.glob().regex(),
codes
rules
);
Some(codes)
Some(rules)
} else if absolute.is_match(file_path) {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.glob().regex(),
codes
rules
);
Some(codes)
Some(rules)
} else {
None
}
})
.flatten()
.copied()
.collect()
}

View File

@@ -0,0 +1,7 @@
//! Utils for reading and writing jupyter notebooks
mod notebook;
mod schema;
pub use notebook::*;
pub use schema::*;

View File

@@ -0,0 +1,283 @@
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::iter;
use std::path::Path;
use serde::Serialize;
use serde_json::error::Category;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::types::Range;
use crate::jupyter::{CellType, JupyterNotebook, SourceValue};
use crate::rules::pycodestyle::rules::SyntaxError;
use crate::IOError;
pub const JUPYTER_NOTEBOOK_EXT: &str = "ipynb";
/// Jupyter Notebook indexing table
///
/// When we lint a jupyter notebook, we have to translate the row/column based on
/// [`crate::message::Location`]
/// to jupyter notebook cell/row/column.
#[derive(Debug, Eq, PartialEq)]
pub struct JupyterIndex {
/// Enter a row (1-based), get back the cell (1-based)
pub row_to_cell: Vec<u32>,
/// Enter a row (1-based), get back the cell (1-based)
pub row_to_row_in_cell: Vec<u32>,
}
/// Return `true` if the [`Path`] appears to be that of a jupyter notebook file (`.ipynb`).
pub fn is_jupyter_notebook(path: &Path) -> bool {
path.extension()
.map_or(false, |ext| ext == JUPYTER_NOTEBOOK_EXT)
// For now this is feature gated here, the long term solution depends on
// https://github.com/charliermarsh/ruff/issues/3410
&& cfg!(feature = "jupyter_notebook")
}
impl JupyterNotebook {
/// See also the black implementation
/// <https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#L1017-L1046>
pub fn read(path: &Path) -> Result<Self, Box<Diagnostic>> {
let reader = BufReader::new(File::open(path).map_err(|err| {
Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
)
})?);
let notebook: JupyterNotebook = match serde_json::from_reader(reader) {
Ok(notebook) => notebook,
Err(err) => {
// Translate the error into a diagnostic
return Err(Box::new({
match err.classify() {
Category::Io => Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
),
Category::Syntax | Category::Eof => {
// Maybe someone saved the python sources (those with the `# %%` separator)
// as jupyter notebook instead. Let's help them.
let contents = std::fs::read_to_string(path).map_err(|err| {
Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
)
})?;
// Check if tokenizing was successful and the file is non-empty
if (ruff_rustpython::tokenize(&contents))
.last()
.map_or(true, Result::is_err)
{
Diagnostic::new(
SyntaxError {
message: format!(
"A Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}) must internally be JSON, \
but this file isn't valid JSON: {err}"
),
},
Range::default(),
)
} else {
Diagnostic::new(
SyntaxError {
message: format!(
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT} extension), \
which must be internally stored as JSON, \
but found a Python source file: {err}"
),
},
Range::default(),
)
}
}
Category::Data => {
// We could try to read the schema version here but if this fails it's
// a bug anyway
Diagnostic::new(
SyntaxError {
message: format!(
"This file does not match the schema expected of Jupyter Notebooks: {err}"
),
},
Range::default(),
)
}
}
}));
}
};
// v4 is what everybody uses
if notebook.nbformat != 4 {
// bail because we should have already failed at the json schema stage
return Err(Box::new(Diagnostic::new(
SyntaxError {
message: format!(
"Expected Jupyter Notebook format 4, found {}",
notebook.nbformat
),
},
Range::default(),
)));
}
Ok(notebook)
}
/// Concatenates all cells into a single virtual file and builds an index that maps the content
/// to notebook cell locations
pub fn index(&self) -> (String, JupyterIndex) {
let mut jupyter_index = JupyterIndex {
// Enter a line number (1-based), get back the cell (1-based)
// 0 index is just padding
row_to_cell: vec![0],
// Enter a line number (1-based), get back the row number in the cell (1-based)
// 0 index is just padding
row_to_row_in_cell: vec![0],
};
let size_hint = self
.cells
.iter()
.filter(|cell| cell.cell_type == CellType::Code)
.count();
let mut contents = Vec::with_capacity(size_hint);
for (pos, cell) in self
.cells
.iter()
.enumerate()
.filter(|(_pos, cell)| cell.cell_type == CellType::Code)
{
let cell_contents = match &cell.source {
SourceValue::String(string) => {
// TODO(konstin): is or isn't there a trailing newline per cell?
// i've only seen these as array and never as string
let line_count = u32::try_from(string.lines().count()).unwrap();
jupyter_index.row_to_cell.extend(
iter::repeat(u32::try_from(pos + 1).unwrap()).take(line_count as usize),
);
jupyter_index.row_to_row_in_cell.extend(1..=line_count);
string.clone()
}
SourceValue::StringArray(string_array) => {
jupyter_index.row_to_cell.extend(
iter::repeat(u32::try_from(pos + 1).unwrap()).take(string_array.len()),
);
jupyter_index
.row_to_row_in_cell
.extend(1..=u32::try_from(string_array.len()).unwrap());
// lines already end in a newline character
string_array.join("")
}
};
contents.push(cell_contents);
}
// The last line doesn't end in a newline character
(contents.join("\n"), jupyter_index)
}
/// Write back with an indent of 1, just like black
pub fn write(&self, path: &Path) -> anyhow::Result<()> {
let mut writer = BufWriter::new(File::create(path)?);
// https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#LL1041
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
self.serialize(&mut ser)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use std::path::Path;
#[cfg(feature = "jupyter_notebook")]
use crate::jupyter::is_jupyter_notebook;
use crate::jupyter::{JupyterIndex, JupyterNotebook};
#[test]
fn test_valid() {
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
assert!(JupyterNotebook::read(path).is_ok());
}
#[test]
fn test_r() {
// We can load this, it will be filtered out later
let path = Path::new("resources/test/fixtures/jupyter/R.ipynb");
assert!(JupyterNotebook::read(path).is_ok());
}
#[test]
fn test_invalid() {
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: Expected a Jupyter Notebook (.ipynb extension), \
which must be internally stored as JSON, \
but found a Python source file: \
expected value at line 1 column 1"
);
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
but this file isn't valid JSON: \
expected value at line 1 column 1"
);
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
missing field `cells` at line 1 column 2"
);
}
#[test]
#[cfg(feature = "jupyter_notebook")]
fn inclusions() {
let path = Path::new("foo/bar/baz");
assert!(!is_jupyter_notebook(path));
let path = Path::new("foo/bar/baz.ipynb");
assert!(is_jupyter_notebook(path));
}
#[test]
fn test_concat_notebook() {
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
let notebook = JupyterNotebook::read(path).unwrap();
let (contents, index) = notebook.index();
assert_eq!(
contents,
r#"def unused_variable():
x = 1
y = 2
print(f"cell one: {y}")
unused_variable()
def mutable_argument(z=set()):
print(f"cell two: {z}")
mutable_argument()
"#
);
assert_eq!(
index,
JupyterIndex {
row_to_cell: vec![0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3],
row_to_row_in_cell: vec![0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4],
}
);
}
}

View File

@@ -0,0 +1,256 @@
//! The JSON schema of a Jupyter Notebook, entrypoint is [`JupyterNotebook`]
//!
//! Generated by <https://app.quicktype.io/> from
//! <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
//! Jupyter Notebook v4.5 JSON schema.
//!
//! The following changes were made to the generated version: `Cell::id` is optional because it
//! wasn't required <v4.5, `#[serde(deny_unknown_fields)]` was added where the schema had
//! `"additionalProperties": false` and `#[serde(flatten)] pub other: BTreeMap<String, Value>`
//! for `"additionalProperties": true` as preparation for round-trip support.
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// The root of the JSON of a Jupyter Notebook
///
/// Generated by <https://app.quicktype.io/> from
/// <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
/// Jupyter Notebook v4.5 JSON schema.
#[derive(Debug, Serialize, Deserialize)]
pub struct JupyterNotebook {
/// Array of cells of the current notebook.
pub cells: Vec<Cell>,
/// Notebook root-level metadata.
pub metadata: JupyterNotebookMetadata,
/// Notebook format (major number). Incremented between backwards incompatible changes to the
/// notebook format.
pub nbformat: i64,
/// Notebook format (minor number). Incremented for backward compatible changes to the
/// notebook format.
pub nbformat_minor: i64,
}
/// Notebook raw nbconvert cell.
///
/// Notebook markdown cell.
///
/// Notebook code cell.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Cell {
pub attachments: Option<HashMap<String, HashMap<String, SourceValue>>>,
/// String identifying the type of cell.
pub cell_type: CellType,
/// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
/// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
/// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
pub id: Option<String>,
/// Cell-level metadata.
pub metadata: CellMetadata,
pub source: SourceValue,
/// The code cell's prompt number. Will be null if the cell has not been run.
pub execution_count: Option<i64>,
/// Execution, display, or stream outputs.
pub outputs: Option<Vec<Output>>,
}
/// Cell-level metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct CellMetadata {
/// Raw cell metadata format for nbconvert.
pub format: Option<String>,
/// Official Jupyter Metadata for Raw Cells
///
/// Official Jupyter Metadata for Markdown Cells
///
/// Official Jupyter Metadata for Code Cells
pub jupyter: Option<HashMap<String, Option<Value>>>,
pub name: Option<String>,
pub tags: Option<Vec<String>>,
/// Whether the cell's output is collapsed/expanded.
pub collapsed: Option<bool>,
/// Execution time for the code in the cell. This tracks time at which messages are received
/// from iopub or shell channels
pub execution: Option<Execution>,
/// Whether the cell's output is scrolled, unscrolled, or autoscrolled.
pub scrolled: Option<ScrolledUnion>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Execution time for the code in the cell. This tracks time at which messages are received
/// from iopub or shell channels
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Execution {
/// header.date (in ISO 8601 format) of iopub channel's execute_input message. It indicates
/// the time at which the kernel broadcasts an execute_input message to connected frontends
#[serde(rename = "iopub.execute_input")]
pub iopub_execute_input: Option<String>,
/// header.date (in ISO 8601 format) of iopub channel's kernel status message when the status
/// is 'busy'
#[serde(rename = "iopub.status.busy")]
pub iopub_status_busy: Option<String>,
/// header.date (in ISO 8601 format) of iopub channel's kernel status message when the status
/// is 'idle'. It indicates the time at which kernel finished processing the associated
/// request
#[serde(rename = "iopub.status.idle")]
pub iopub_status_idle: Option<String>,
/// header.date (in ISO 8601 format) of the shell channel's execute_reply message. It
/// indicates the time at which the execute_reply message was created
#[serde(rename = "shell.execute_reply")]
pub shell_execute_reply: Option<String>,
}
/// Result of executing a code cell.
///
/// Data displayed as a result of code cell execution.
///
/// Stream output from a code cell.
///
/// Output of an error that occurred during code cell execution.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Output {
pub data: Option<HashMap<String, SourceValue>>,
/// A result's prompt number.
pub execution_count: Option<i64>,
pub metadata: Option<HashMap<String, Option<Value>>>,
/// Type of cell output.
pub output_type: OutputType,
/// The name of the stream (stdout, stderr).
pub name: Option<String>,
/// The stream's text output, represented as an array of strings.
pub text: Option<TextUnion>,
/// The name of the error.
pub ename: Option<String>,
/// The value, or message, of the error.
pub evalue: Option<String>,
/// The error's traceback, represented as an array of strings.
pub traceback: Option<Vec<String>>,
}
/// Notebook root-level metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct JupyterNotebookMetadata {
/// The author(s) of the notebook document
pub authors: Option<Vec<Option<Value>>>,
/// Kernel information.
pub kernelspec: Option<Kernelspec>,
/// Kernel information.
pub language_info: Option<LanguageInfo>,
/// Original notebook format (major number) before converting the notebook between versions.
/// This should never be written to a file.
pub orig_nbformat: Option<i64>,
/// The title of the notebook document
pub title: Option<String>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Kernel information.
#[derive(Debug, Serialize, Deserialize)]
pub struct Kernelspec {
/// Name to display in UI.
pub display_name: String,
/// Name of the kernel specification.
pub name: String,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Kernel information.
#[derive(Debug, Serialize, Deserialize)]
pub struct LanguageInfo {
/// The codemirror mode to use for code in this language.
pub codemirror_mode: Option<CodemirrorMode>,
/// The file extension for files in this language.
pub file_extension: Option<String>,
/// The mimetype corresponding to files in this language.
pub mimetype: Option<String>,
/// The programming language which this kernel runs.
pub name: String,
/// The pygments lexer to use for code in this language.
pub pygments_lexer: Option<String>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// mimetype output (e.g. text/plain), represented as either an array of strings or a
/// string.
///
/// Contents of the cell, represented as an array of lines.
///
/// The stream's text output, represented as an array of strings.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceValue {
String(String),
StringArray(Vec<String>),
}
/// Whether the cell's output is scrolled, unscrolled, or autoscrolled.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ScrolledUnion {
Bool(bool),
Enum(ScrolledEnum),
}
/// mimetype output (e.g. text/plain), represented as either an array of strings or a
/// string.
///
/// Contents of the cell, represented as an array of lines.
///
/// The stream's text output, represented as an array of strings.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TextUnion {
String(String),
StringArray(Vec<String>),
}
/// The codemirror mode to use for code in this language.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CodemirrorMode {
AnythingMap(HashMap<String, Option<Value>>),
String(String),
}
/// String identifying the type of cell.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum CellType {
#[serde(rename = "code")]
Code,
#[serde(rename = "markdown")]
Markdown,
#[serde(rename = "raw")]
Raw,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ScrolledEnum {
#[serde(rename = "auto")]
Auto,
}
/// Type of cell output.
#[derive(Debug, Serialize, Deserialize)]
pub enum OutputType {
#[serde(rename = "display_data")]
DisplayData,
#[serde(rename = "error")]
Error,
#[serde(rename = "execute_result")]
ExecuteResult,
#[serde(rename = "stream")]
Stream,
}

View File

@@ -20,6 +20,7 @@ mod docstrings;
pub mod fix;
pub mod flake8_to_ruff;
pub mod fs;
pub mod jupyter;
mod lex;
pub mod linter;
pub mod logging;

View File

@@ -204,7 +204,7 @@ pub fn check_path(
if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores);
if !ignores.is_empty() {
diagnostics.retain(|diagnostic| !ignores.contains(&diagnostic.kind.rule()));
diagnostics.retain(|diagnostic| !ignores.contains(diagnostic.kind.rule()));
}
};

View File

@@ -8,7 +8,7 @@ use log::warn;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
@@ -17,7 +17,7 @@ use ruff_python_ast::source_code::{LineEnding, Locator};
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::registry::{AsRule, Rule};
use crate::registry::{AsRule, Rule, RuleSet};
use crate::rule_redirects::get_redirect_target;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
@@ -28,49 +28,6 @@ static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
});
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
#[derive(Debug)]
pub enum Exemption<'a> {
None,
All,
Codes(Vec<&'a str>),
}
/// Return `true` if a file is exempt from checking based on the contents of the
/// given line.
pub fn extract_file_exemption(line: &str) -> Exemption {
let line = line.trim_start();
if line.starts_with("# flake8: noqa")
|| line.starts_with("# flake8: NOQA")
|| line.starts_with("# flake8: NoQA")
{
return Exemption::All;
}
if let Some(remainder) = line
.strip_prefix("# ruff: noqa")
.or_else(|| line.strip_prefix("# ruff: NOQA"))
.or_else(|| line.strip_prefix("# ruff: NoQA"))
{
if remainder.is_empty() {
return Exemption::All;
} else if let Some(codes) = remainder.strip_prefix(':') {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
return Exemption::Codes(codes);
}
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
}
Exemption::None
}
#[derive(Debug)]
pub enum Directive<'a> {
None,
@@ -119,6 +76,47 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
}
}
enum ParsedExemption<'a> {
None,
All,
Codes(Vec<&'a str>),
}
/// Return a [`ParsedExemption`] for a given comment line.
fn parse_file_exemption(line: &str) -> ParsedExemption {
let line = line.trim_start();
if line.starts_with("# flake8: noqa")
|| line.starts_with("# flake8: NOQA")
|| line.starts_with("# flake8: NoQA")
{
return ParsedExemption::All;
}
if let Some(remainder) = line
.strip_prefix("# ruff: noqa")
.or_else(|| line.strip_prefix("# ruff: NOQA"))
.or_else(|| line.strip_prefix("# ruff: NoQA"))
{
if remainder.is_empty() {
return ParsedExemption::All;
} else if let Some(codes) = remainder.strip_prefix(':') {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
return ParsedExemption::Codes(codes);
}
warn!("Unexpected suffix on `noqa` directive: \"{line}\"");
}
ParsedExemption::None
}
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
@@ -147,6 +145,43 @@ pub fn rule_is_ignored(
}
}
pub enum FileExemption {
None,
All,
Codes(Vec<NoqaCode>),
}
/// Extract the [`FileExemption`] for a given Python source file, enumerating any rules that are
/// globally ignored within the file.
pub fn file_exemption(lines: &[&str], commented_lines: &[usize]) -> FileExemption {
let mut exempt_codes: Vec<NoqaCode> = vec![];
for lineno in commented_lines {
match parse_file_exemption(lines[lineno - 1]) {
ParsedExemption::All => {
return FileExemption::All;
}
ParsedExemption::Codes(codes) => {
exempt_codes.extend(codes.into_iter().filter_map(|code| {
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
Some(rule.noqa_code())
} else {
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
None
}
}));
}
ParsedExemption::None => {}
}
}
if exempt_codes.is_empty() {
FileExemption::None
} else {
FileExemption::Codes(exempt_codes)
}
}
pub fn add_noqa(
path: &Path,
diagnostics: &[Diagnostic],
@@ -174,46 +209,28 @@ fn add_noqa_inner(
line_ending: &LineEnding,
) -> (usize, String) {
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
let mut matches_by_line: FxHashMap<usize, FxHashSet<Rule>> = FxHashMap::default();
// Whether the file is exempted from all checks.
let mut file_exempted = false;
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
let mut matches_by_line: FxHashMap<usize, RuleSet> = FxHashMap::default();
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
file_exempted = true;
}
Exemption::Codes(codes) => {
file_exemptions.extend(codes.into_iter().filter_map(|code| {
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code)) {
Some(rule.noqa_code())
} else {
warn!("Invalid code provided to `# ruff: noqa`: {}", code);
None
}
}));
}
Exemption::None => {}
}
}
// Whether the file is exempted from all checks.
// Codes that are globally exempted (within the current file).
let exemption = file_exemption(&lines, commented_lines);
// Mark any non-ignored diagnostics.
for diagnostic in diagnostics {
// If the file is exempted, don't add any noqa directives.
if file_exempted {
continue;
}
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
if !file_exemptions.is_empty() {
if file_exemptions.contains(&diagnostic.kind.rule().noqa_code()) {
match &exemption {
FileExemption::All => {
// If the file is exempted, don't add any noqa directives.
continue;
}
FileExemption::Codes(codes) => {
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
continue;
}
}
FileExemption::None => {}
}
// Is the violation ignored by a `noqa` directive on the parent line?
@@ -280,7 +297,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
push_codes(&mut output, rules.iter().map(Rule::noqa_code));
push_codes(&mut output, rules.iter().map(|rule| rule.noqa_code()));
output.push_str(line_ending);
count += 1;
}

View File

@@ -1,5 +1,7 @@
//! Registry of all [`Rule`] implementations.
mod rule_set;
use strum_macros::{AsRefStr, EnumIter};
use ruff_diagnostics::Violation;
@@ -7,71 +9,72 @@ use ruff_macros::RuleNamespace;
use crate::codes::{self, RuleCodePrefix};
use crate::rules;
pub use rule_set::{RuleSet, RuleSetIterator};
ruff_macros::register_rules!(
// pycodestyle errors
rules::pycodestyle::rules::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::OverIndented,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespace,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -92,19 +95,19 @@ ruff_macros::register_rules!(
rules::pycodestyle::rules::IOError,
rules::pycodestyle::rules::SyntaxError,
// pycodestyle warnings
rules::pycodestyle::rules::IndentationContainsTabs,
rules::pycodestyle::rules::TabIndentation,
rules::pycodestyle::rules::TrailingWhitespace,
rules::pycodestyle::rules::NoNewLineAtEndOfFile,
rules::pycodestyle::rules::BlankLineContainsWhitespace,
rules::pycodestyle::rules::MissingNewlineAtEndOfFile,
rules::pycodestyle::rules::BlankLineWithWhitespace,
rules::pycodestyle::rules::DocLineTooLong,
rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
rules::pyflakes::rules::UnusedImport,
rules::pyflakes::rules::ImportShadowedByLoopVar,
rules::pyflakes::rules::ImportStar,
rules::pyflakes::rules::UndefinedLocalWithImportStar,
rules::pyflakes::rules::LateFutureImport,
rules::pyflakes::rules::ImportStarUsage,
rules::pyflakes::rules::ImportStarNotPermitted,
rules::pyflakes::rules::UndefinedLocalWithImportStarUsage,
rules::pyflakes::rules::UndefinedLocalWithNestedImportStarUsage,
rules::pyflakes::rules::FutureFeatureNotDefined,
rules::pyflakes::rules::PercentFormatInvalidFormat,
rules::pyflakes::rules::PercentFormatExpectedMapping,
@@ -124,7 +127,7 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::MultiValueRepeatedKeyLiteral,
rules::pyflakes::rules::MultiValueRepeatedKeyVariable,
rules::pyflakes::rules::ExpressionsInStarAssignment,
rules::pyflakes::rules::TwoStarredExpressions,
rules::pyflakes::rules::MultipleStarredExpressions,
rules::pyflakes::rules::AssertTuple,
rules::pyflakes::rules::IsLiteral,
rules::pyflakes::rules::InvalidPrintSyntax,
@@ -143,6 +146,8 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::UnusedAnnotation,
rules::pyflakes::rules::RaiseNotImplemented,
// pylint
rules::pylint::rules::AssertOnStringLiteral,
rules::pylint::rules::UselessReturn,
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
@@ -150,6 +155,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::InvalidEnvvarValue,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::BinaryOpException,
rules::pylint::rules::InvalidCharacterBackspace,
rules::pylint::rules::InvalidCharacterSub,
rules::pylint::rules::InvalidCharacterEsc,
@@ -161,15 +167,15 @@ ruff_macros::register_rules!(
rules::pylint::rules::UselessImportAlias,
rules::pylint::rules::UnnecessaryDirectLambdaCall,
rules::pylint::rules::NonlocalWithoutBinding,
rules::pylint::rules::UsedPriorGlobalDeclaration,
rules::pylint::rules::LoadBeforeGlobalDeclaration,
rules::pylint::rules::AwaitOutsideAsync,
rules::pylint::rules::PropertyWithParameters,
rules::pylint::rules::ReturnInInit,
rules::pylint::rules::ConsiderUsingFromImport,
rules::pylint::rules::ManualFromImport,
rules::pylint::rules::CompareToEmptyString,
rules::pylint::rules::ComparisonOfConstant,
rules::pylint::rules::ConsiderMergingIsinstance,
rules::pylint::rules::ConsiderUsingSysExit,
rules::pylint::rules::RepeatedIsinstanceCalls,
rules::pylint::rules::SysExitAlias,
rules::pylint::rules::MagicValueComparison,
rules::pylint::rules::UselessElseOnLoop,
rules::pylint::rules::GlobalStatement,
@@ -193,7 +199,7 @@ ruff_macros::register_rules!(
rules::flake8_bugbear::rules::MutableArgumentDefault,
rules::flake8_bugbear::rules::NoExplicitStacklevel,
rules::flake8_bugbear::rules::UnusedLoopControlVariable,
rules::flake8_bugbear::rules::FunctionCallArgumentDefault,
rules::flake8_bugbear::rules::FunctionCallInDefaultArgument,
rules::flake8_bugbear::rules::GetAttrWithConstant,
rules::flake8_bugbear::rules::SetAttrWithConstant,
rules::flake8_bugbear::rules::AssertFalse,
@@ -258,8 +264,8 @@ ruff_macros::register_rules!(
rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
// flake8-print
rules::flake8_print::rules::PrintFound,
rules::flake8_print::rules::PPrintFound,
rules::flake8_print::rules::Print,
rules::flake8_print::rules::PPrint,
// flake8-quotes
rules::flake8_quotes::rules::BadQuotesInlineString,
rules::flake8_quotes::rules::BadQuotesMultilineString,
@@ -271,38 +277,38 @@ ruff_macros::register_rules!(
rules::flake8_annotations::rules::MissingTypeKwargs,
rules::flake8_annotations::rules::MissingTypeSelf,
rules::flake8_annotations::rules::MissingTypeCls,
rules::flake8_annotations::rules::MissingReturnTypePublicFunction,
rules::flake8_annotations::rules::MissingReturnTypeUndocumentedPublicFunction,
rules::flake8_annotations::rules::MissingReturnTypePrivateFunction,
rules::flake8_annotations::rules::MissingReturnTypeSpecialMethod,
rules::flake8_annotations::rules::MissingReturnTypeStaticMethod,
rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
rules::flake8_annotations::rules::AnyType,
// flake8-2020
rules::flake8_2020::rules::SysVersionSlice3Referenced,
rules::flake8_2020::rules::SysVersion2Referenced,
rules::flake8_2020::rules::SysVersionSlice3,
rules::flake8_2020::rules::SysVersion2,
rules::flake8_2020::rules::SysVersionCmpStr3,
rules::flake8_2020::rules::SysVersionInfo0Eq3Referenced,
rules::flake8_2020::rules::SixPY3Referenced,
rules::flake8_2020::rules::SysVersionInfo0Eq3,
rules::flake8_2020::rules::SixPY3,
rules::flake8_2020::rules::SysVersionInfo1CmpInt,
rules::flake8_2020::rules::SysVersionInfoMinorCmpInt,
rules::flake8_2020::rules::SysVersion0Referenced,
rules::flake8_2020::rules::SysVersion0,
rules::flake8_2020::rules::SysVersionCmpStr10,
rules::flake8_2020::rules::SysVersionSlice1Referenced,
rules::flake8_2020::rules::SysVersionSlice1,
// flake8-simplify
rules::flake8_simplify::rules::ManualDictLookup,
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictLookup,
rules::flake8_simplify::rules::DuplicateIsinstanceCall,
rules::flake8_simplify::rules::CollapsibleIf,
rules::flake8_simplify::rules::NeedlessBool,
rules::flake8_simplify::rules::UseContextlibSuppress,
rules::flake8_simplify::rules::SuppressibleException,
rules::flake8_simplify::rules::ReturnInTryExceptFinally,
rules::flake8_simplify::rules::UseTernaryOperator,
rules::flake8_simplify::rules::IfElseBlockInsteadOfIfExp,
rules::flake8_simplify::rules::CompareWithTuple,
rules::flake8_simplify::rules::ReimplementedBuiltin,
rules::flake8_simplify::rules::UseCapitalEnvironmentVariables,
rules::flake8_simplify::rules::UncapitalizedEnvironmentVariables,
rules::flake8_simplify::rules::IfWithSameArms,
rules::flake8_simplify::rules::OpenFileWithContextHandler,
rules::flake8_simplify::rules::MultipleWithStatements,
rules::flake8_simplify::rules::KeyInDict,
rules::flake8_simplify::rules::InDictKeys,
rules::flake8_simplify::rules::NegateEqualOp,
rules::flake8_simplify::rules::NegateNotEqualOp,
rules::flake8_simplify::rules::DoubleNegation,
@@ -314,14 +320,14 @@ ruff_macros::register_rules!(
rules::flake8_simplify::rules::ExprOrTrue,
rules::flake8_simplify::rules::ExprAndFalse,
rules::flake8_simplify::rules::YodaConditions,
rules::flake8_simplify::rules::DictGetWithDefault,
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet,
// pyupgrade
rules::pyupgrade::rules::UselessMetaclassType,
rules::pyupgrade::rules::TypeOfPrimitive,
rules::pyupgrade::rules::UselessObjectInheritance,
rules::pyupgrade::rules::DeprecatedUnittestAlias,
rules::pyupgrade::rules::DeprecatedCollectionType,
rules::pyupgrade::rules::TypingUnion,
rules::pyupgrade::rules::NonPEP585Annotation,
rules::pyupgrade::rules::NonPEP604Annotation,
rules::pyupgrade::rules::SuperCallWithParameters,
rules::pyupgrade::rules::UTF8EncodingDeclaration,
rules::pyupgrade::rules::UnnecessaryFutureImport,
@@ -336,31 +342,31 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::OpenAlias,
rules::pyupgrade::rules::ReplaceUniversalNewlines,
rules::pyupgrade::rules::ReplaceStdoutStderr,
rules::pyupgrade::rules::RewriteCElementTree,
rules::pyupgrade::rules::DeprecatedCElementTree,
rules::pyupgrade::rules::OSErrorAlias,
rules::pyupgrade::rules::RewriteUnicodeLiteral,
rules::pyupgrade::rules::RewriteMockImport,
rules::pyupgrade::rules::RewriteListComprehension,
rules::pyupgrade::rules::RewriteYieldFrom,
rules::pyupgrade::rules::UnicodeKindPrefix,
rules::pyupgrade::rules::DeprecatedMockImport,
rules::pyupgrade::rules::UnpackedListComprehension,
rules::pyupgrade::rules::YieldInForLoop,
rules::pyupgrade::rules::UnnecessaryBuiltinImport,
rules::pyupgrade::rules::FormatLiterals,
rules::pyupgrade::rules::PrintfStringFormatting,
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::LRUCacheWithMaxsizeNone,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
rules::pyupgrade::rules::NonPEP604Isinstance,
// pydocstyle
rules::pydocstyle::rules::PublicModule,
rules::pydocstyle::rules::PublicClass,
rules::pydocstyle::rules::PublicMethod,
rules::pydocstyle::rules::PublicFunction,
rules::pydocstyle::rules::PublicPackage,
rules::pydocstyle::rules::MagicMethod,
rules::pydocstyle::rules::PublicNestedClass,
rules::pydocstyle::rules::PublicInit,
rules::pydocstyle::rules::UndocumentedPublicModule,
rules::pydocstyle::rules::UndocumentedPublicClass,
rules::pydocstyle::rules::UndocumentedPublicMethod,
rules::pydocstyle::rules::UndocumentedPublicFunction,
rules::pydocstyle::rules::UndocumentedPublicPackage,
rules::pydocstyle::rules::UndocumentedMagicMethod,
rules::pydocstyle::rules::UndocumentedPublicNestedClass,
rules::pydocstyle::rules::UndocumentedPublicInit,
rules::pydocstyle::rules::FitsOnOneLine,
rules::pydocstyle::rules::NoBlankLineBeforeFunction,
rules::pydocstyle::rules::NoBlankLineAfterFunction,
@@ -368,11 +374,11 @@ ruff_macros::register_rules!(
rules::pydocstyle::rules::OneBlankLineAfterClass,
rules::pydocstyle::rules::BlankLineAfterSummary,
rules::pydocstyle::rules::IndentWithSpaces,
rules::pydocstyle::rules::NoUnderIndentation,
rules::pydocstyle::rules::NoOverIndentation,
rules::pydocstyle::rules::UnderIndentation,
rules::pydocstyle::rules::OverIndentation,
rules::pydocstyle::rules::NewLineAfterLastParagraph,
rules::pydocstyle::rules::NoSurroundingWhitespace,
rules::pydocstyle::rules::NoBlankLineBeforeClass,
rules::pydocstyle::rules::SurroundingWhitespace,
rules::pydocstyle::rules::BlankLineBeforeClass,
rules::pydocstyle::rules::MultiLineSummaryFirstLine,
rules::pydocstyle::rules::MultiLineSummarySecondLine,
rules::pydocstyle::rules::SectionNotOverIndented,
@@ -389,9 +395,9 @@ ruff_macros::register_rules!(
rules::pydocstyle::rules::DashedUnderlineAfterSection,
rules::pydocstyle::rules::SectionUnderlineAfterName,
rules::pydocstyle::rules::SectionUnderlineMatchesSectionLength,
rules::pydocstyle::rules::BlankLineAfterSection,
rules::pydocstyle::rules::BlankLineBeforeSection,
rules::pydocstyle::rules::NoBlankLinesBetweenHeaderAndContent,
rules::pydocstyle::rules::NoBlankLineAfterSection,
rules::pydocstyle::rules::NoBlankLineBeforeSection,
rules::pydocstyle::rules::BlankLinesBetweenHeaderAndContent,
rules::pydocstyle::rules::BlankLineAfterLastSection,
rules::pydocstyle::rules::EmptyDocstringSection,
rules::pydocstyle::rules::EndsInPunctuation,
@@ -423,24 +429,45 @@ ruff_macros::register_rules!(
rules::eradicate::rules::CommentedOutCode,
// flake8-bandit
rules::flake8_bandit::rules::Assert,
rules::flake8_bandit::rules::ExecBuiltin,
rules::flake8_bandit::rules::BadFilePermissions,
rules::flake8_bandit::rules::ExecBuiltin,
rules::flake8_bandit::rules::HardcodedBindAllInterfaces,
rules::flake8_bandit::rules::HardcodedPasswordString,
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
rules::flake8_bandit::rules::HardcodedPasswordDefault,
rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
rules::flake8_bandit::rules::HardcodedPasswordString,
rules::flake8_bandit::rules::HardcodedSQLExpression,
rules::flake8_bandit::rules::HardcodedTempFile,
rules::flake8_bandit::rules::TryExceptPass,
rules::flake8_bandit::rules::TryExceptContinue,
rules::flake8_bandit::rules::RequestWithoutTimeout,
rules::flake8_bandit::rules::HashlibInsecureHashFunction,
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
rules::flake8_bandit::rules::RequestWithNoCertValidation,
rules::flake8_bandit::rules::UnsafeYAMLLoad,
rules::flake8_bandit::rules::RequestWithoutTimeout,
rules::flake8_bandit::rules::SnmpInsecureVersion,
rules::flake8_bandit::rules::SnmpWeakCryptography,
rules::flake8_bandit::rules::LoggingConfigInsecureListen,
rules::flake8_bandit::rules::Jinja2AutoescapeFalse,
rules::flake8_bandit::rules::SuspiciousEvalUsage,
rules::flake8_bandit::rules::SuspiciousFTPLibUsage,
rules::flake8_bandit::rules::SuspiciousInsecureCipherUsage,
rules::flake8_bandit::rules::SuspiciousInsecureCipherModeUsage,
rules::flake8_bandit::rules::SuspiciousInsecureHashUsage,
rules::flake8_bandit::rules::SuspiciousMarkSafeUsage,
rules::flake8_bandit::rules::SuspiciousMarshalUsage,
rules::flake8_bandit::rules::SuspiciousMktempUsage,
rules::flake8_bandit::rules::SuspiciousNonCryptographicRandomUsage,
rules::flake8_bandit::rules::SuspiciousPickleUsage,
rules::flake8_bandit::rules::SuspiciousTelnetUsage,
rules::flake8_bandit::rules::SuspiciousURLOpenUsage,
rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage,
rules::flake8_bandit::rules::SuspiciousXMLCElementTreeUsage,
rules::flake8_bandit::rules::SuspiciousXMLETreeUsage,
rules::flake8_bandit::rules::SuspiciousXMLElementTreeUsage,
rules::flake8_bandit::rules::SuspiciousXMLExpatBuilderUsage,
rules::flake8_bandit::rules::SuspiciousXMLExpatReaderUsage,
rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage,
rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage,
rules::flake8_bandit::rules::SuspiciousXMLSaxUsage,
rules::flake8_bandit::rules::TryExceptContinue,
rules::flake8_bandit::rules::TryExceptPass,
rules::flake8_bandit::rules::UnsafeYAMLLoad,
// flake8-boolean-trap
rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition,
rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition,
@@ -464,85 +491,85 @@ ruff_macros::register_rules!(
rules::flake8_datetimez::rules::CallDateToday,
rules::flake8_datetimez::rules::CallDateFromtimestamp,
// pygrep-hooks
rules::pygrep_hooks::rules::NoEval,
rules::pygrep_hooks::rules::Eval,
rules::pygrep_hooks::rules::DeprecatedLogWarn,
rules::pygrep_hooks::rules::BlanketTypeIgnore,
rules::pygrep_hooks::rules::BlanketNOQA,
// pandas-vet
rules::pandas_vet::rules::UseOfInplaceArgument,
rules::pandas_vet::rules::UseOfDotIsNull,
rules::pandas_vet::rules::UseOfDotNotNull,
rules::pandas_vet::rules::UseOfDotIx,
rules::pandas_vet::rules::UseOfDotAt,
rules::pandas_vet::rules::UseOfDotIat,
rules::pandas_vet::rules::UseOfDotPivotOrUnstack,
rules::pandas_vet::rules::UseOfDotValues,
rules::pandas_vet::rules::UseOfDotReadTable,
rules::pandas_vet::rules::UseOfDotStack,
rules::pandas_vet::rules::UseOfPdMerge,
rules::pandas_vet::rules::DfIsABadVariableName,
rules::pandas_vet::rules::PandasUseOfInplaceArgument,
rules::pandas_vet::rules::PandasUseOfDotIsNull,
rules::pandas_vet::rules::PandasUseOfDotNotNull,
rules::pandas_vet::rules::PandasUseOfDotIx,
rules::pandas_vet::rules::PandasUseOfDotAt,
rules::pandas_vet::rules::PandasUseOfDotIat,
rules::pandas_vet::rules::PandasUseOfDotPivotOrUnstack,
rules::pandas_vet::rules::PandasUseOfDotValues,
rules::pandas_vet::rules::PandasUseOfDotReadTable,
rules::pandas_vet::rules::PandasUseOfDotStack,
rules::pandas_vet::rules::PandasUseOfPdMerge,
rules::pandas_vet::rules::PandasDfVariableName,
// flake8-errmsg
rules::flake8_errmsg::rules::RawStringInException,
rules::flake8_errmsg::rules::FStringInException,
rules::flake8_errmsg::rules::DotFormatInException,
// flake8-pyi
rules::flake8_pyi::rules::PrefixTypeParams,
rules::flake8_pyi::rules::UnprefixedTypeParam,
rules::flake8_pyi::rules::BadVersionInfoComparison,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
rules::flake8_pyi::rules::ArgumentDefaultInStub,
rules::flake8_pyi::rules::TypeCommentInStub,
// flake8-pytest-style
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
rules::flake8_pytest_style::rules::FixturePositionalArgs,
rules::flake8_pytest_style::rules::ExtraneousScopeFunction,
rules::flake8_pytest_style::rules::MissingFixtureNameUnderscore,
rules::flake8_pytest_style::rules::IncorrectFixtureNameUnderscore,
rules::flake8_pytest_style::rules::ParametrizeNamesWrongType,
rules::flake8_pytest_style::rules::ParametrizeValuesWrongType,
rules::flake8_pytest_style::rules::PatchWithLambda,
rules::flake8_pytest_style::rules::UnittestAssertion,
rules::flake8_pytest_style::rules::RaisesWithoutException,
rules::flake8_pytest_style::rules::RaisesTooBroad,
rules::flake8_pytest_style::rules::RaisesWithMultipleStatements,
rules::flake8_pytest_style::rules::IncorrectPytestImport,
rules::flake8_pytest_style::rules::AssertAlwaysFalse,
rules::flake8_pytest_style::rules::FailWithoutMessage,
rules::flake8_pytest_style::rules::AssertInExcept,
rules::flake8_pytest_style::rules::CompositeAssertion,
rules::flake8_pytest_style::rules::FixtureParamWithoutValue,
rules::flake8_pytest_style::rules::DeprecatedYieldFixture,
rules::flake8_pytest_style::rules::FixtureFinalizerCallback,
rules::flake8_pytest_style::rules::UselessYieldFixture,
rules::flake8_pytest_style::rules::IncorrectMarkParenthesesStyle,
rules::flake8_pytest_style::rules::UnnecessaryAsyncioMarkOnFixture,
rules::flake8_pytest_style::rules::ErroneousUseFixturesOnFixture,
rules::flake8_pytest_style::rules::UseFixturesWithoutParameters,
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
rules::flake8_pytest_style::rules::PytestExtraneousScopeFunction,
rules::flake8_pytest_style::rules::PytestMissingFixtureNameUnderscore,
rules::flake8_pytest_style::rules::PytestIncorrectFixtureNameUnderscore,
rules::flake8_pytest_style::rules::PytestParametrizeNamesWrongType,
rules::flake8_pytest_style::rules::PytestParametrizeValuesWrongType,
rules::flake8_pytest_style::rules::PytestPatchWithLambda,
rules::flake8_pytest_style::rules::PytestUnittestAssertion,
rules::flake8_pytest_style::rules::PytestRaisesWithoutException,
rules::flake8_pytest_style::rules::PytestRaisesTooBroad,
rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements,
rules::flake8_pytest_style::rules::PytestIncorrectPytestImport,
rules::flake8_pytest_style::rules::PytestAssertAlwaysFalse,
rules::flake8_pytest_style::rules::PytestFailWithoutMessage,
rules::flake8_pytest_style::rules::PytestAssertInExcept,
rules::flake8_pytest_style::rules::PytestCompositeAssertion,
rules::flake8_pytest_style::rules::PytestFixtureParamWithoutValue,
rules::flake8_pytest_style::rules::PytestDeprecatedYieldFixture,
rules::flake8_pytest_style::rules::PytestFixtureFinalizerCallback,
rules::flake8_pytest_style::rules::PytestUselessYieldFixture,
rules::flake8_pytest_style::rules::PytestIncorrectMarkParenthesesStyle,
rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture,
rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture,
rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters,
// flake8-pie
rules::flake8_pie::rules::UnnecessaryPass,
rules::flake8_pie::rules::DupeClassFieldDefinitions,
rules::flake8_pie::rules::PreferUniqueEnums,
rules::flake8_pie::rules::DuplicateClassFieldDefinition,
rules::flake8_pie::rules::NonUniqueEnums,
rules::flake8_pie::rules::UnnecessarySpread,
rules::flake8_pie::rules::UnnecessaryDictKwargs,
rules::flake8_pie::rules::PreferListBuiltin,
rules::flake8_pie::rules::SingleStartsEndsWith,
rules::flake8_pie::rules::ReimplementedListBuiltin,
rules::flake8_pie::rules::MultipleStartsEndsWith,
rules::flake8_pie::rules::UnnecessaryComprehensionAnyAll,
// flake8-commas
rules::flake8_commas::rules::TrailingCommaMissing,
rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
rules::flake8_commas::rules::TrailingCommaProhibited,
rules::flake8_commas::rules::MissingTrailingComma,
rules::flake8_commas::rules::TrailingCommaOnBareTuple,
rules::flake8_commas::rules::ProhibitedTrailingComma,
// flake8-no-pep420
rules::flake8_no_pep420::rules::ImplicitNamespacePackage,
// flake8-executable
rules::flake8_executable::rules::ShebangNotExecutable,
rules::flake8_executable::rules::ShebangMissingExecutableFile,
rules::flake8_executable::rules::ShebangPython,
rules::flake8_executable::rules::ShebangWhitespace,
rules::flake8_executable::rules::ShebangNewline,
rules::flake8_executable::rules::ShebangMissingPython,
rules::flake8_executable::rules::ShebangLeadingWhitespace,
rules::flake8_executable::rules::ShebangNotFirstLine,
// flake8-type-checking
rules::flake8_type_checking::rules::TypingOnlyFirstPartyImport,
rules::flake8_type_checking::rules::TypingOnlyThirdPartyImport,
@@ -552,7 +579,7 @@ ruff_macros::register_rules!(
// tryceratops
rules::tryceratops::rules::RaiseVanillaClass,
rules::tryceratops::rules::RaiseVanillaArgs,
rules::tryceratops::rules::PreferTypeError,
rules::tryceratops::rules::TypeCheckWithoutTypeError,
rules::tryceratops::rules::ReraiseNoCause,
rules::tryceratops::rules::VerboseRaise,
rules::tryceratops::rules::TryConsiderElse,
@@ -560,31 +587,31 @@ ruff_macros::register_rules!(
rules::tryceratops::rules::ErrorInsteadOfException,
rules::tryceratops::rules::VerboseLogMessage,
// flake8-use-pathlib
rules::flake8_use_pathlib::violations::PathlibAbspath,
rules::flake8_use_pathlib::violations::PathlibChmod,
rules::flake8_use_pathlib::violations::PathlibMkdir,
rules::flake8_use_pathlib::violations::PathlibMakedirs,
rules::flake8_use_pathlib::violations::PathlibRename,
rules::flake8_use_pathlib::violations::OsPathAbspath,
rules::flake8_use_pathlib::violations::OsChmod,
rules::flake8_use_pathlib::violations::OsMkdir,
rules::flake8_use_pathlib::violations::OsMakedirs,
rules::flake8_use_pathlib::violations::OsRename,
rules::flake8_use_pathlib::violations::PathlibReplace,
rules::flake8_use_pathlib::violations::PathlibRmdir,
rules::flake8_use_pathlib::violations::PathlibRemove,
rules::flake8_use_pathlib::violations::PathlibUnlink,
rules::flake8_use_pathlib::violations::PathlibGetcwd,
rules::flake8_use_pathlib::violations::PathlibExists,
rules::flake8_use_pathlib::violations::PathlibExpanduser,
rules::flake8_use_pathlib::violations::PathlibIsDir,
rules::flake8_use_pathlib::violations::PathlibIsFile,
rules::flake8_use_pathlib::violations::PathlibIsLink,
rules::flake8_use_pathlib::violations::PathlibReadlink,
rules::flake8_use_pathlib::violations::PathlibStat,
rules::flake8_use_pathlib::violations::PathlibIsAbs,
rules::flake8_use_pathlib::violations::PathlibJoin,
rules::flake8_use_pathlib::violations::PathlibBasename,
rules::flake8_use_pathlib::violations::PathlibDirname,
rules::flake8_use_pathlib::violations::PathlibSamefile,
rules::flake8_use_pathlib::violations::PathlibSplitext,
rules::flake8_use_pathlib::violations::PathlibOpen,
rules::flake8_use_pathlib::violations::PathlibPyPath,
rules::flake8_use_pathlib::violations::OsRmdir,
rules::flake8_use_pathlib::violations::OsRemove,
rules::flake8_use_pathlib::violations::OsUnlink,
rules::flake8_use_pathlib::violations::OsGetcwd,
rules::flake8_use_pathlib::violations::OsPathExists,
rules::flake8_use_pathlib::violations::OsPathExpanduser,
rules::flake8_use_pathlib::violations::OsPathIsdir,
rules::flake8_use_pathlib::violations::OsPathIsfile,
rules::flake8_use_pathlib::violations::OsPathIslink,
rules::flake8_use_pathlib::violations::OsReadlink,
rules::flake8_use_pathlib::violations::OsStat,
rules::flake8_use_pathlib::violations::OsPathIsabs,
rules::flake8_use_pathlib::violations::OsPathJoin,
rules::flake8_use_pathlib::violations::OsPathBasename,
rules::flake8_use_pathlib::violations::OsPathDirname,
rules::flake8_use_pathlib::violations::OsPathSamefile,
rules::flake8_use_pathlib::violations::OsPathSplitext,
rules::flake8_use_pathlib::violations::BuiltinOpen,
rules::flake8_use_pathlib::violations::PyPath,
// flake8-logging-format
rules::flake8_logging_format::violations::LoggingStringFormat,
rules::flake8_logging_format::violations::LoggingPercentFormat,
@@ -605,17 +632,18 @@ ruff_macros::register_rules!(
rules::ruff::rules::AmbiguousUnicodeCharacterString,
rules::ruff::rules::AmbiguousUnicodeCharacterDocstring,
rules::ruff::rules::AmbiguousUnicodeCharacterComment,
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::CollectionLiteralConcatenation,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
rules::ruff::rules::PairwiseOverZipped,
// flake8-django
rules::flake8_django::rules::NullableModelStringField,
rules::flake8_django::rules::LocalsInRenderFunction,
rules::flake8_django::rules::ExcludeWithModelForm,
rules::flake8_django::rules::AllWithModelForm,
rules::flake8_django::rules::ModelWithoutDunderStr,
rules::flake8_django::rules::NonLeadingReceiverDecorator,
rules::flake8_django::rules::DjangoNullableModelStringField,
rules::flake8_django::rules::DjangoLocalsInRenderFunction,
rules::flake8_django::rules::DjangoExcludeWithModelForm,
rules::flake8_django::rules::DjangoAllWithModelForm,
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
);
pub trait AsRule {
@@ -840,17 +868,17 @@ impl Rule {
| Rule::DocLineTooLong
| Rule::LineTooLong
| Rule::MixedSpacesAndTabs
| Rule::NoNewLineAtEndOfFile
| Rule::MissingNewlineAtEndOfFile
| Rule::UTF8EncodingDeclaration
| Rule::ShebangMissingExecutableFile
| Rule::ShebangNotExecutable
| Rule::ShebangNewline
| Rule::ShebangNotFirstLine
| Rule::BidirectionalUnicode
| Rule::ShebangPython
| Rule::ShebangWhitespace
| Rule::ShebangMissingPython
| Rule::ShebangLeadingWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
| Rule::TabIndentation
| Rule::BlankLineWithWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -868,17 +896,17 @@ impl Rule {
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::SingleLineImplicitStringConcatenation
| Rule::TrailingCommaMissing
| Rule::TrailingCommaOnBareTupleProhibited
| Rule::MissingTrailingComma
| Rule::TrailingCommaOnBareTuple
| Rule::MultipleStatementsOnOneLineColon
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited
| Rule::ProhibitedTrailingComma
| Rule::TypeCommentInStub => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MissingWhitespace
@@ -918,7 +946,7 @@ impl Rule {
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
(
Rule::NoBlankLineBeforeClass,
Rule::BlankLineBeforeClass,
Rule::OneBlankLineBeforeClass,
"`one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are \
incompatible. Ignoring `one-blank-line-before-class`.",

View File

@@ -0,0 +1,365 @@
use crate::registry::Rule;
use ruff_macros::CacheKey;
use std::fmt::{Debug, Formatter};
use std::iter::FusedIterator;
/// A set of [`Rule`]s.
///
/// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set.
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
pub struct RuleSet([u64; 9]);
impl RuleSet {
const EMPTY: [u64; 9] = [0; 9];
// 64 fits into a u16 without truncation
#[allow(clippy::cast_possible_truncation)]
const SLICE_BITS: u16 = u64::BITS as u16;
/// Returns an empty rule set.
pub const fn empty() -> Self {
Self(Self::EMPTY)
}
pub fn clear(&mut self) {
self.0 = Self::EMPTY;
}
#[inline]
pub const fn from_rule(rule: Rule) -> Self {
let rule = rule as u16;
let index = (rule / Self::SLICE_BITS) as usize;
debug_assert!(
index < Self::EMPTY.len(),
"Rule index out of bounds. Increase the size of the bitset array."
);
// The bit-position of this specific rule in the slice
let shift = rule % Self::SLICE_BITS;
// Set the index for that rule to 1
let mask = 1 << shift;
let mut bits = Self::EMPTY;
bits[index] = mask;
Self(bits)
}
#[inline]
pub const fn from_rules(rules: &[Rule]) -> Self {
let mut set = RuleSet::empty();
let mut i = 0;
// Uses a while because for loops are not allowed in const functions.
while i < rules.len() {
set = set.union(&RuleSet::from_rule(rules[i]));
i += 1;
}
set
}
/// Returns the union of the two rule sets `self` and `other`
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
/// let set_2 = RuleSet::from_rules(&[
/// Rule::BadQuotesInlineString,
/// Rule::BooleanPositionalValueInFunctionCall,
/// ]);
///
/// let union = set_1.union(&set_2);
///
/// assert!(union.contains(Rule::AmbiguousFunctionName));
/// assert!(union.contains(Rule::AnyType));
/// assert!(union.contains(Rule::BadQuotesInlineString));
/// assert!(union.contains(Rule::BooleanPositionalValueInFunctionCall));
/// ```
#[must_use]
pub const fn union(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] |= other.0[i];
i += 1;
}
self
}
/// Returns `self` without any of the rules contained in `other`.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
/// let set_2 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::Debugger]);
///
/// let subtract = set_1.subtract(&set_2);
///
/// assert!(subtract.contains(Rule::AnyType));
/// assert!(!subtract.contains(Rule::AmbiguousFunctionName));
/// ```
#[must_use]
pub const fn subtract(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] ^= other.0[i];
i += 1;
}
self
}
/// Returns true if `self` and `other` contain at least one common rule.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// assert!(set_1.intersects(&RuleSet::from_rules(&[
/// Rule::AnyType,
/// Rule::BadQuotesInlineString
/// ])));
///
/// assert!(!set_1.intersects(&RuleSet::from_rules(&[
/// Rule::BooleanPositionalValueInFunctionCall,
/// Rule::BadQuotesInlineString
/// ])));
/// ```
pub const fn intersects(&self, other: &Self) -> bool {
let mut i = 0;
while i < self.0.len() {
if self.0[i] & other.0[i] != 0 {
return true;
}
i += 1;
}
false
}
/// Returns `true` if this set contains no rules, `false` otherwise.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// assert!(RuleSet::empty().is_empty());
/// assert!(
/// !RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::BadQuotesInlineString])
/// .is_empty()
/// );
/// ```
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of rules in this set.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// assert_eq!(RuleSet::empty().len(), 0);
/// assert_eq!(
/// RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::BadQuotesInlineString]).len(),
/// 2
/// );
pub const fn len(&self) -> usize {
let mut len: u32 = 0;
let mut i = 0;
while i < self.0.len() {
len += self.0[i].count_ones();
i += 1;
}
len as usize
}
/// Inserts `rule` into the set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let mut set = RuleSet::empty();
///
/// assert!(!set.contains(Rule::AnyType));
///
/// set.insert(Rule::AnyType);
///
/// assert!(set.contains(Rule::AnyType));
/// ```
pub fn insert(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_rule(rule));
}
/// Removes `rule` from the set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let mut set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// set.remove(Rule::AmbiguousFunctionName);
///
/// assert!(set.contains(Rule::AnyType));
/// assert!(!set.contains(Rule::AmbiguousFunctionName));
/// ```
pub fn remove(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.subtract(&RuleSet::from_rule(rule));
}
/// Returns `true` if `rule` is in this set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// assert!(set.contains(Rule::AmbiguousFunctionName));
/// assert!(!set.contains(Rule::BreakOutsideLoop));
/// ```
pub const fn contains(&self, rule: Rule) -> bool {
let rule = rule as u16;
let index = rule as usize / Self::SLICE_BITS as usize;
let shift = rule % Self::SLICE_BITS;
let mask = 1 << shift;
self.0[index] & mask != 0
}
/// Returns an iterator over the rules in this set.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// let iter: Vec<_> = set.iter().collect();
///
/// assert_eq!(iter, vec![Rule::AmbiguousFunctionName, Rule::AnyType]);
/// ```
pub fn iter(&self) -> RuleSetIterator {
RuleSetIterator {
set: self.clone(),
index: 0,
}
}
}
impl Debug for RuleSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl FromIterator<Rule> for RuleSet {
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
let mut set = RuleSet::empty();
for rule in iter {
set.insert(rule);
}
set
}
}
impl Extend<Rule> for RuleSet {
fn extend<T: IntoIterator<Item = Rule>>(&mut self, iter: T) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_iter(iter));
}
}
impl IntoIterator for RuleSet {
type Item = Rule;
type IntoIter = RuleSetIterator;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for &RuleSet {
type Item = Rule;
type IntoIter = RuleSetIterator;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct RuleSetIterator {
set: RuleSet,
index: u16,
}
impl Iterator for RuleSetIterator {
type Item = Rule;
fn next(&mut self) -> Option<Self::Item> {
loop {
let slice = self.set.0.get_mut(self.index as usize)?;
// `trailing_zeros` is guaranteed to return a value in [0;64]
#[allow(clippy::cast_possible_truncation)]
let bit = slice.trailing_zeros() as u16;
if bit < RuleSet::SLICE_BITS {
*slice ^= 1 << bit;
let rule_value = self.index * RuleSet::SLICE_BITS + bit;
// SAFETY: RuleSet guarantees that only valid rules are stored in the set.
#[allow(unsafe_code)]
return Some(unsafe { std::mem::transmute(rule_value) });
}
self.index += 1;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.set.len();
(len, Some(len))
}
}
impl ExactSizeIterator for RuleSetIterator {}
impl FusedIterator for RuleSetIterator {}
#[cfg(test)]
mod tests {
use crate::registry::{Rule, RuleSet};
use strum::IntoEnumIterator;
/// Tests that the set can contain all rules
#[test]
fn test_all_rules() {
for rule in Rule::iter() {
let set = RuleSet::from_rule(rule);
assert!(set.contains(rule));
}
let all_rules_set: RuleSet = Rule::iter().collect();
let all_rules: Vec<_> = all_rules_set.iter().collect();
let expected_rules: Vec<_> = Rule::iter().collect();
assert_eq!(all_rules, expected_rules);
}
}

View File

@@ -14,6 +14,7 @@ use ruff_python_stdlib::path::is_python_file;
use rustc_hash::FxHashSet;
use crate::fs;
use crate::jupyter::is_jupyter_notebook;
use crate::settings::configuration::Configuration;
use crate::settings::pyproject::settings_toml;
use crate::settings::{pyproject, AllSettings, Settings};
@@ -193,15 +194,15 @@ fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::Gl
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
}
/// Return `true` if the [`DirEntry`] appears to be that of a Python file.
/// Return `true` if the [`DirEntry`] appears to be that of a Python file or jupyter notebook.
pub fn is_python_entry(entry: &DirEntry) -> bool {
is_python_file(entry.path())
(is_python_file(entry.path()) || is_jupyter_notebook(entry.path()))
&& !entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
}
/// Find all Python (`.py` and `.pyi` files) in a set of paths.
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
pub fn python_files_in_path(
paths: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
@@ -260,38 +261,6 @@ pub fn python_files_in_path(
std::sync::Mutex::new(vec![]);
walker.run(|| {
Box::new(|result| {
// Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents.
if pyproject_strategy.is_hierarchical() {
if let Ok(entry) = &result {
if entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
{
match settings_toml(entry.path()) {
Ok(Some(pyproject)) => match resolve_scoped_settings(
&pyproject,
&Relativity::Parent,
processor,
) {
Ok((root, settings)) => {
resolver.write().unwrap().add(root, settings);
}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
},
Ok(None) => {}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
}
}
}
}
// Respect our own exclusion behavior.
if let Ok(entry) = &result {
if entry.depth() > 0 {
@@ -324,6 +293,38 @@ pub fn python_files_in_path(
}
}
// Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents.
if pyproject_strategy.is_hierarchical() {
if let Ok(entry) = &result {
if entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
{
match settings_toml(entry.path()) {
Ok(Some(pyproject)) => match resolve_scoped_settings(
&pyproject,
&Relativity::Parent,
processor,
) {
Ok((root, settings)) => {
resolver.write().unwrap().add(root, settings);
}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
},
Ok(None) => {}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
}
}
}
}
if result.as_ref().map_or(true, |entry| {
// Accept all files that are passed-in directly.
(entry.depth() == 0 && entry.file_type().map_or(false, |ft| ft.is_file()))

View File

@@ -13,16 +13,16 @@ mod tests {
use crate::settings;
use crate::test::test_path;
#[test_case(Rule::SysVersionSlice3Referenced, Path::new("YTT101.py"); "YTT101")]
#[test_case(Rule::SysVersion2Referenced, Path::new("YTT102.py"); "YTT102")]
#[test_case(Rule::SysVersionSlice3, Path::new("YTT101.py"); "YTT101")]
#[test_case(Rule::SysVersion2, Path::new("YTT102.py"); "YTT102")]
#[test_case(Rule::SysVersionCmpStr3, Path::new("YTT103.py"); "YTT103")]
#[test_case(Rule::SysVersionInfo0Eq3Referenced, Path::new("YTT201.py"); "YTT201")]
#[test_case(Rule::SixPY3Referenced, Path::new("YTT202.py"); "YTT202")]
#[test_case(Rule::SysVersionInfo0Eq3, Path::new("YTT201.py"); "YTT201")]
#[test_case(Rule::SixPY3, Path::new("YTT202.py"); "YTT202")]
#[test_case(Rule::SysVersionInfo1CmpInt, Path::new("YTT203.py"); "YTT203")]
#[test_case(Rule::SysVersionInfoMinorCmpInt, Path::new("YTT204.py"); "YTT204")]
#[test_case(Rule::SysVersion0Referenced, Path::new("YTT301.py"); "YTT301")]
#[test_case(Rule::SysVersion0, Path::new("YTT301.py"); "YTT301")]
#[test_case(Rule::SysVersionCmpStr10, Path::new("YTT302.py"); "YTT302")]
#[test_case(Rule::SysVersionSlice1Referenced, Path::new("YTT303.py"); "YTT303")]
#[test_case(Rule::SysVersionSlice1, Path::new("YTT303.py"); "YTT303")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -9,9 +9,9 @@ use crate::checkers::ast::Checker;
use crate::registry::Rule;
#[violation]
pub struct SysVersionSlice3Referenced;
pub struct SysVersionSlice3;
impl Violation for SysVersionSlice3Referenced {
impl Violation for SysVersionSlice3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[:3]` referenced (python3.10), use `sys.version_info`")
@@ -19,9 +19,9 @@ impl Violation for SysVersionSlice3Referenced {
}
#[violation]
pub struct SysVersion2Referenced;
pub struct SysVersion2;
impl Violation for SysVersion2Referenced {
impl Violation for SysVersion2 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[2]` referenced (python3.10), use `sys.version_info`")
@@ -39,9 +39,9 @@ impl Violation for SysVersionCmpStr3 {
}
#[violation]
pub struct SysVersionInfo0Eq3Referenced;
pub struct SysVersionInfo0Eq3;
impl Violation for SysVersionInfo0Eq3Referenced {
impl Violation for SysVersionInfo0Eq3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version_info[0] == 3` referenced (python4), use `>=`")
@@ -49,9 +49,9 @@ impl Violation for SysVersionInfo0Eq3Referenced {
}
#[violation]
pub struct SixPY3Referenced;
pub struct SixPY3;
impl Violation for SixPY3Referenced {
impl Violation for SixPY3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`six.PY3` referenced (python4), use `not six.PY2`")
@@ -85,9 +85,9 @@ impl Violation for SysVersionInfoMinorCmpInt {
}
#[violation]
pub struct SysVersion0Referenced;
pub struct SysVersion0;
impl Violation for SysVersion0Referenced {
impl Violation for SysVersion0 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[0]` referenced (python10), use `sys.version_info`")
@@ -105,9 +105,9 @@ impl Violation for SysVersionCmpStr10 {
}
#[violation]
pub struct SysVersionSlice1Referenced;
pub struct SysVersionSlice1;
impl Violation for SysVersionSlice1Referenced {
impl Violation for SysVersionSlice1 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[:1]` referenced (python10), use `sys.version_info`")
@@ -137,25 +137,17 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
} = &upper.node
{
if *i == BigInt::from(1)
&& checker
.settings
.rules
.enabled(Rule::SysVersionSlice1Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionSlice1)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice1Referenced,
Range::from(value),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice1, Range::from(value)));
} else if *i == BigInt::from(3)
&& checker
.settings
.rules
.enabled(Rule::SysVersionSlice3Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionSlice3)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice3Referenced,
Range::from(value),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice3, Range::from(value)));
}
}
}
@@ -164,18 +156,15 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
value: Constant::Int(i),
..
} => {
if *i == BigInt::from(2)
&& checker.settings.rules.enabled(Rule::SysVersion2Referenced)
if *i == BigInt::from(2) && checker.settings.rules.enabled(Rule::SysVersion2) {
checker
.diagnostics
.push(Diagnostic::new(SysVersion2, Range::from(value)));
} else if *i == BigInt::from(0) && checker.settings.rules.enabled(Rule::SysVersion0)
{
checker
.diagnostics
.push(Diagnostic::new(SysVersion2Referenced, Range::from(value)));
} else if *i == BigInt::from(0)
&& checker.settings.rules.enabled(Rule::SysVersion0Referenced)
{
checker
.diagnostics
.push(Diagnostic::new(SysVersion0Referenced, Range::from(value)));
.push(Diagnostic::new(SysVersion0, Range::from(value)));
}
}
@@ -207,15 +196,11 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
) = (ops, comparators)
{
if *n == BigInt::from(3)
&& checker
.settings
.rules
.enabled(Rule::SysVersionInfo0Eq3Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionInfo0Eq3)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionInfo0Eq3Referenced,
Range::from(left),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfo0Eq3, Range::from(left)));
}
}
} else if *i == BigInt::from(1) {
@@ -309,6 +294,6 @@ pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
{
checker
.diagnostics
.push(Diagnostic::new(SixPY3Referenced, Range::from(expr)));
.push(Diagnostic::new(SixPY3, Range::from(expr)));
}
}

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -29,7 +29,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersion2Referenced
name: SysVersion2
body: "`sys.version[2]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersion2Referenced
name: SysVersion2
body: "`sys.version[2]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -29,7 +29,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -42,7 +42,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SixPY3Referenced
name: SixPY3
body: "`six.PY3` referenced (python4), use `not six.PY2`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SixPY3Referenced
name: SixPY3
body: "`six.PY3` referenced (python4), use `not six.PY2`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersion0Referenced
name: SysVersion0
body: "`sys.version[0]` referenced (python10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersion0Referenced
name: SysVersion0
body: "`sys.version[0]` referenced (python10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersionSlice1Referenced
name: SysVersionSlice1
body: "`sys.version[:1]` referenced (python10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionSlice1Referenced
name: SysVersionSlice1
body: "`sys.version[:1]` referenced (python10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -7,7 +7,7 @@ use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
/// ANN204
pub fn add_return_none_annotation(locator: &Locator, stmt: &Stmt) -> Result<Fix> {
pub fn add_return_annotation(locator: &Locator, stmt: &Stmt, annotation: &str) -> Result<Fix> {
let range = Range::from(stmt);
let contents = locator.slice(range);
@@ -18,7 +18,7 @@ pub fn add_return_none_annotation(locator: &Locator, stmt: &Stmt) -> Result<Fix>
for (start, tok, ..) in lexer::lex_located(contents, Mode::Module, range.location).flatten() {
if seen_lpar && seen_rpar {
if matches!(tok, Tok::Colon) {
return Ok(Fix::insertion(" -> None".to_string(), start));
return Ok(Fix::insertion(format!(" -> {annotation}"), start));
}
}

View File

@@ -26,7 +26,7 @@ mod tests {
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
Rule::MissingTypeCls,
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -54,7 +54,7 @@ mod tests {
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
Rule::MissingTypeCls,
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -99,7 +99,7 @@ mod tests {
..Default::default()
},
..Settings::for_rules(vec![
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -126,7 +126,7 @@ mod tests {
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
Rule::MissingTypeCls,
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -161,7 +161,7 @@ mod tests {
Path::new("flake8_annotations/allow_overload.py"),
&Settings {
..Settings::for_rules(vec![
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -179,7 +179,7 @@ mod tests {
Path::new("flake8_annotations/allow_nested_overload.py"),
&Settings {
..Settings::for_rules(vec![
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
@@ -190,4 +190,14 @@ mod tests {
assert_yaml_snapshot!(diagnostics);
Ok(())
}
#[test]
fn simple_magic_methods() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_annotations/simple_magic_methods.py"),
&Settings::for_rule(Rule::MissingReturnTypeSpecialMethod),
)?;
assert_yaml_snapshot!(diagnostics);
Ok(())
}
}

View File

@@ -5,10 +5,11 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::ReturnStatementVisitor;
use ruff_python_ast::types::Range;
use ruff_python_ast::visibility;
use ruff_python_ast::visibility::Visibility;
use ruff_python_ast::visibility::{self};
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{cast, helpers};
use ruff_python_stdlib::typing::SIMPLE_MAGIC_RETURN_TYPES;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::{Definition, DefinitionKind};
@@ -209,14 +210,14 @@ impl Violation for MissingTypeCls {
/// return a + b
/// ```
#[violation]
pub struct MissingReturnTypePublicFunction {
pub struct MissingReturnTypeUndocumentedPublicFunction {
pub name: String,
}
impl Violation for MissingReturnTypePublicFunction {
impl Violation for MissingReturnTypeUndocumentedPublicFunction {
#[derive_message_formats]
fn message(&self) -> String {
let MissingReturnTypePublicFunction { name } = self;
let MissingReturnTypeUndocumentedPublicFunction { name } = self;
format!("Missing return type annotation for public function `{name}`")
}
}
@@ -650,7 +651,7 @@ pub fn definition(
helpers::identifier_range(stmt, checker.locator),
));
}
} else if is_method && visibility::is_init(cast::name(stmt)) {
} else if is_method && visibility::is_init(name) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if checker
@@ -667,7 +668,7 @@ pub fn definition(
helpers::identifier_range(stmt, checker.locator),
);
if checker.patch(diagnostic.kind.rule()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
match fixes::add_return_annotation(checker.locator, stmt, "None") {
Ok(fix) => {
diagnostic.amend(fix);
}
@@ -677,18 +678,30 @@ pub fn definition(
diagnostics.push(diagnostic);
}
}
} else if is_method && visibility::is_magic(cast::name(stmt)) {
} else if is_method && visibility::is_magic(name) {
if checker
.settings
.rules
.enabled(Rule::MissingReturnTypeSpecialMethod)
{
diagnostics.push(Diagnostic::new(
let mut diagnostic = Diagnostic::new(
MissingReturnTypeSpecialMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
);
let return_type = SIMPLE_MAGIC_RETURN_TYPES.get(name);
if let Some(return_type) = return_type {
if checker.patch(diagnostic.kind.rule()) {
match fixes::add_return_annotation(checker.locator, stmt, return_type) {
Ok(fix) => {
diagnostic.amend(fix);
}
Err(e) => error!("Failed to generate fix: {e}"),
}
}
}
diagnostics.push(diagnostic);
}
} else {
match visibility {
@@ -696,10 +709,10 @@ pub fn definition(
if checker
.settings
.rules
.enabled(Rule::MissingReturnTypePublicFunction)
.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypePublicFunction {
MissingReturnTypeUndocumentedPublicFunction {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `bar`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false
@@ -42,7 +42,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false
@@ -81,7 +81,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false
@@ -94,7 +94,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `error_partially_typed_1`"
suggestion: ~
fixable: false
@@ -42,7 +42,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `error_partially_typed_3`"
suggestion: ~
fixable: false
@@ -55,7 +55,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `error_typed_self`"
suggestion: ~
fixable: false

View File

@@ -0,0 +1,285 @@
---
source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__str__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 2
column: 8
end_location:
row: 2
column: 15
fix:
content: " -> str"
location:
row: 2
column: 21
end_location:
row: 2
column: 21
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__repr__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 5
column: 8
end_location:
row: 5
column: 16
fix:
content: " -> str"
location:
row: 5
column: 22
end_location:
row: 5
column: 22
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__len__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 8
column: 8
end_location:
row: 8
column: 15
fix:
content: " -> int"
location:
row: 8
column: 21
end_location:
row: 8
column: 21
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__length_hint__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 11
column: 8
end_location:
row: 11
column: 23
fix:
content: " -> int"
location:
row: 11
column: 29
end_location:
row: 11
column: 29
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__init__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 14
column: 8
end_location:
row: 14
column: 16
fix:
content: " -> None"
location:
row: 14
column: 22
end_location:
row: 14
column: 22
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__del__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 17
column: 8
end_location:
row: 17
column: 15
fix:
content: " -> None"
location:
row: 17
column: 21
end_location:
row: 17
column: 21
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__bool__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 20
column: 8
end_location:
row: 20
column: 16
fix:
content: " -> bool"
location:
row: 20
column: 22
end_location:
row: 20
column: 22
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__bytes__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 23
column: 8
end_location:
row: 23
column: 17
fix:
content: " -> bytes"
location:
row: 23
column: 23
end_location:
row: 23
column: 23
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__format__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 26
column: 8
end_location:
row: 26
column: 18
fix:
content: " -> str"
location:
row: 26
column: 37
end_location:
row: 26
column: 37
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__contains__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 29
column: 8
end_location:
row: 29
column: 20
fix:
content: " -> bool"
location:
row: 29
column: 32
end_location:
row: 29
column: 32
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__complex__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 32
column: 8
end_location:
row: 32
column: 19
fix:
content: " -> complex"
location:
row: 32
column: 25
end_location:
row: 32
column: 25
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__int__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 35
column: 8
end_location:
row: 35
column: 15
fix:
content: " -> int"
location:
row: 35
column: 21
end_location:
row: 35
column: 21
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__float__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 38
column: 8
end_location:
row: 38
column: 17
fix:
content: " -> float"
location:
row: 38
column: 23
end_location:
row: 38
column: 23
parent: ~
- kind:
name: MissingReturnTypeSpecialMethod
body: "Missing return type annotation for special method `__index__`"
suggestion: "Add `None` return type"
fixable: true
location:
row: 41
column: 8
end_location:
row: 41
column: 17
fix:
content: " -> int"
location:
row: 41
column: 23
end_location:
row: 41
column: 23
parent: ~

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: MissingReturnTypePublicFunction
name: MissingReturnTypeUndocumentedPublicFunction
body: "Missing return type annotation for public function `foo`"
suggestion: ~
fixable: false

View File

@@ -16,24 +16,26 @@ mod tests {
use crate::test::test_path;
#[test_case(Rule::Assert, Path::new("S101.py"); "S101")]
#[test_case(Rule::ExecBuiltin, Path::new("S102.py"); "S102")]
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"); "S103")]
#[test_case(Rule::ExecBuiltin, Path::new("S102.py"); "S102")]
#[test_case(Rule::HardcodedBindAllInterfaces, Path::new("S104.py"); "S104")]
#[test_case(Rule::HardcodedPasswordString, Path::new("S105.py"); "S105")]
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"); "S106")]
#[test_case(Rule::HardcodedPasswordDefault, Path::new("S107.py"); "S107")]
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"); "S106")]
#[test_case(Rule::HardcodedPasswordString, Path::new("S105.py"); "S105")]
#[test_case(Rule::HardcodedSQLExpression, Path::new("S608.py"); "S608")]
#[test_case(Rule::HardcodedTempFile, Path::new("S108.py"); "S108")]
#[test_case(Rule::RequestWithoutTimeout, Path::new("S113.py"); "S113")]
#[test_case(Rule::HashlibInsecureHashFunction, Path::new("S324.py"); "S324")]
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
#[test_case(Rule::RequestWithNoCertValidation, Path::new("S501.py"); "S501")]
#[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")]
#[test_case(Rule::RequestWithoutTimeout, Path::new("S113.py"); "S113")]
#[test_case(Rule::SnmpInsecureVersion, Path::new("S508.py"); "S508")]
#[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"); "S509")]
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
#[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")]
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"); "S301")]
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"); "S312")]
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"); "S112")]
#[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")]
#[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -24,6 +24,16 @@ pub use request_with_no_cert_validation::{
pub use request_without_timeout::{request_without_timeout, RequestWithoutTimeout};
pub use snmp_insecure_version::{snmp_insecure_version, SnmpInsecureVersion};
pub use snmp_weak_cryptography::{snmp_weak_cryptography, SnmpWeakCryptography};
pub use suspicious_function_call::{
suspicious_function_call, SuspiciousEvalUsage, SuspiciousFTPLibUsage,
SuspiciousInsecureCipherModeUsage, SuspiciousInsecureCipherUsage, SuspiciousInsecureHashUsage,
SuspiciousMarkSafeUsage, SuspiciousMarshalUsage, SuspiciousMktempUsage,
SuspiciousNonCryptographicRandomUsage, SuspiciousPickleUsage, SuspiciousTelnetUsage,
SuspiciousURLOpenUsage, SuspiciousUnverifiedContextUsage, SuspiciousXMLCElementTreeUsage,
SuspiciousXMLETreeUsage, SuspiciousXMLElementTreeUsage, SuspiciousXMLExpatBuilderUsage,
SuspiciousXMLExpatReaderUsage, SuspiciousXMLMiniDOMUsage, SuspiciousXMLPullDOMUsage,
SuspiciousXMLSaxUsage,
};
pub use try_except_continue::{try_except_continue, TryExceptContinue};
pub use try_except_pass::{try_except_pass, TryExceptPass};
pub use unsafe_yaml_load::{unsafe_yaml_load, UnsafeYAMLLoad};
@@ -44,6 +54,7 @@ mod request_with_no_cert_validation;
mod request_without_timeout;
mod snmp_insecure_version;
mod snmp_weak_cryptography;
mod suspicious_function_call;
mod try_except_continue;
mod try_except_pass;
mod unsafe_yaml_load;

View File

@@ -0,0 +1,519 @@
//! Check for calls to suspicious functions, or calls into suspicious modules.
//!
//! See: <https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html>
use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
#[violation]
pub struct SuspiciousPickleUsage;
impl Violation for SuspiciousPickleUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue")
}
}
#[violation]
pub struct SuspiciousMarshalUsage;
impl Violation for SuspiciousMarshalUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Deserialization with the `marshal` module is possibly dangerous")
}
}
#[violation]
pub struct SuspiciousInsecureHashUsage;
impl Violation for SuspiciousInsecureHashUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of insecure MD2, MD4, MD5, or SHA1 hash function")
}
}
#[violation]
pub struct SuspiciousInsecureCipherUsage;
impl Violation for SuspiciousInsecureCipherUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of insecure cipher, replace with a known secure cipher such as AES")
}
}
#[violation]
pub struct SuspiciousInsecureCipherModeUsage;
impl Violation for SuspiciousInsecureCipherModeUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of insecure cipher mode, replace with a known secure cipher such as AES")
}
}
#[violation]
pub struct SuspiciousMktempUsage;
impl Violation for SuspiciousMktempUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of insecure and deprecated function (`mktemp`)")
}
}
#[violation]
pub struct SuspiciousEvalUsage;
impl Violation for SuspiciousEvalUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of possibly insecure function; consider using `ast.literal_eval`")
}
}
#[violation]
pub struct SuspiciousMarkSafeUsage;
impl Violation for SuspiciousMarkSafeUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of `mark_safe` may expose cross-site scripting vulnerabilities")
}
}
#[violation]
pub struct SuspiciousURLOpenUsage;
impl Violation for SuspiciousURLOpenUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.")
}
}
#[violation]
pub struct SuspiciousNonCryptographicRandomUsage;
impl Violation for SuspiciousNonCryptographicRandomUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Standard pseudo-random generators are not suitable for cryptographic purposes")
}
}
#[violation]
pub struct SuspiciousXMLCElementTreeUsage;
impl Violation for SuspiciousXMLCElementTreeUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLElementTreeUsage;
impl Violation for SuspiciousXMLElementTreeUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLExpatReaderUsage;
impl Violation for SuspiciousXMLExpatReaderUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLExpatBuilderUsage;
impl Violation for SuspiciousXMLExpatBuilderUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLSaxUsage;
impl Violation for SuspiciousXMLSaxUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLMiniDOMUsage;
impl Violation for SuspiciousXMLMiniDOMUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLPullDOMUsage;
impl Violation for SuspiciousXMLPullDOMUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousXMLETreeUsage;
impl Violation for SuspiciousXMLETreeUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents")
}
}
#[violation]
pub struct SuspiciousUnverifiedContextUsage;
impl Violation for SuspiciousUnverifiedContextUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Python allows using an insecure context via the `_create_unverified_context` that reverts to the previous behavior that does not validate certificates or perform hostname checks.")
}
}
#[violation]
pub struct SuspiciousTelnetUsage;
impl Violation for SuspiciousTelnetUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol.")
}
}
#[violation]
pub struct SuspiciousFTPLibUsage;
impl Violation for SuspiciousFTPLibUsage {
#[derive_message_formats]
fn message(&self) -> String {
format!("FTP-related functions are being called. FTP is considered insecure. Use SSH/SFTP/SCP or some other encrypted protocol.")
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Reason {
Pickle,
Marshal,
InsecureHash,
InsecureCipher,
InsecureCipherMode,
Mktemp,
Eval,
MarkSafe,
URLOpen,
NonCryptographicRandom,
XMLCElementTree,
XMLElementTree,
XMLExpatReader,
XMLExpatBuilder,
XMLSax,
XMLMiniDOM,
XMLPullDOM,
XMLETree,
UnverifiedContext,
Telnet,
FTPLib,
}
struct SuspiciousMembers<'a> {
members: &'a [&'a [&'a str]],
reason: Reason,
}
impl<'a> SuspiciousMembers<'a> {
pub const fn new(members: &'a [&'a [&'a str]], reason: Reason) -> Self {
Self { members, reason }
}
}
struct SuspiciousModule<'a> {
name: &'a str,
reason: Reason,
}
impl<'a> SuspiciousModule<'a> {
pub const fn new(name: &'a str, reason: Reason) -> Self {
Self { name, reason }
}
}
const SUSPICIOUS_MEMBERS: &[SuspiciousMembers] = &[
SuspiciousMembers::new(
&[
&["pickle", "loads"],
&["pickle", "load"],
&["pickle", "Unpickler"],
&["dill", "loads"],
&["dill", "load"],
&["dill", "Unpickler"],
&["shelve", "open"],
&["shelve", "DbfilenameShelf"],
&["jsonpickle", "decode"],
&["jsonpickle", "unpickler", "decode"],
&["pandas", "read_pickle"],
],
Reason::Pickle,
),
SuspiciousMembers::new(
&[&["marshal", "loads"], &["marshal", "load"]],
Reason::Marshal,
),
SuspiciousMembers::new(
&[
&["Crypto", "Hash", "MD5", "new"],
&["Crypto", "Hash", "MD4", "new"],
&["Crypto", "Hash", "MD3", "new"],
&["Crypto", "Hash", "MD2", "new"],
&["Crypto", "Hash", "SHA", "new"],
&["Cryptodome", "Hash", "MD5", "new"],
&["Cryptodome", "Hash", "MD4", "new"],
&["Cryptodome", "Hash", "MD3", "new"],
&["Cryptodome", "Hash", "MD2", "new"],
&["Cryptodome", "Hash", "SHA", "new"],
&["cryptography", "hazmat", "primitives", "hashes", "MD5"],
&["cryptography", "hazmat", "primitives", "hashes", "SHA1"],
],
Reason::InsecureHash,
),
SuspiciousMembers::new(
&[
&["Crypto", "Cipher", "ARC2", "new"],
&["Crypto", "Cipher", "ARC2", "new"],
&["Crypto", "Cipher", "Blowfish", "new"],
&["Crypto", "Cipher", "DES", "new"],
&["Crypto", "Cipher", "XOR", "new"],
&["Cryptodome", "Cipher", "ARC2", "new"],
&["Cryptodome", "Cipher", "ARC2", "new"],
&["Cryptodome", "Cipher", "Blowfish", "new"],
&["Cryptodome", "Cipher", "DES", "new"],
&["Cryptodome", "Cipher", "XOR", "new"],
&[
"cryptography",
"hazmat",
"primitives",
"ciphers",
"algorithms",
"ARC4",
],
&[
"cryptography",
"hazmat",
"primitives",
"ciphers",
"algorithms",
"Blowfish",
],
&[
"cryptography",
"hazmat",
"primitives",
"ciphers",
"algorithms",
"IDEA",
],
],
Reason::InsecureCipher,
),
SuspiciousMembers::new(
&[&[
"cryptography",
"hazmat",
"primitives",
"ciphers",
"modes",
"ECB",
]],
Reason::InsecureCipherMode,
),
SuspiciousMembers::new(&[&["tempfile", "mktemp"]], Reason::Mktemp),
SuspiciousMembers::new(&[&["eval"]], Reason::Eval),
SuspiciousMembers::new(
&[&["django", "utils", "safestring", "mark_safe"]],
Reason::MarkSafe,
),
SuspiciousMembers::new(
&[
&["urllib", "urlopen"],
&["urllib", "request", "urlopen"],
&["urllib", "urlretrieve"],
&["urllib", "request", "urlretrieve"],
&["urllib", "URLopener"],
&["urllib", "request", "URLopener"],
&["urllib", "FancyURLopener"],
&["urllib", "request", "FancyURLopener"],
&["urllib2", "urlopen"],
&["urllib2", "Request"],
&["six", "moves", "urllib", "request", "urlopen"],
&["six", "moves", "urllib", "request", "urlretrieve"],
&["six", "moves", "urllib", "request", "URLopener"],
&["six", "moves", "urllib", "request", "FancyURLopener"],
],
Reason::URLOpen,
),
SuspiciousMembers::new(
&[
&["random", "random"],
&["random", "randrange"],
&["random", "randint"],
&["random", "choice"],
&["random", "choices"],
&["random", "uniform"],
&["random", "triangular"],
],
Reason::NonCryptographicRandom,
),
SuspiciousMembers::new(
&[&["ssl", "_create_unverified_context"]],
Reason::UnverifiedContext,
),
SuspiciousMembers::new(
&[
&["xml", "etree", "cElementTree", "parse"],
&["xml", "etree", "cElementTree", "iterparse"],
&["xml", "etree", "cElementTree", "fromstring"],
&["xml", "etree", "cElementTree", "XMLParser"],
],
Reason::XMLCElementTree,
),
SuspiciousMembers::new(
&[
&["xml", "etree", "ElementTree", "parse"],
&["xml", "etree", "ElementTree", "iterparse"],
&["xml", "etree", "ElementTree", "fromstring"],
&["xml", "etree", "ElementTree", "XMLParser"],
],
Reason::XMLElementTree,
),
SuspiciousMembers::new(
&[&["xml", "sax", "expatreader", "create_parser"]],
Reason::XMLExpatReader,
),
SuspiciousMembers::new(
&[
&["xml", "dom", "expatbuilder", "parse"],
&["xml", "dom", "expatbuilder", "parseString"],
],
Reason::XMLExpatBuilder,
),
SuspiciousMembers::new(
&[
&["xml", "sax", "parse"],
&["xml", "sax", "parseString"],
&["xml", "sax", "make_parser"],
],
Reason::XMLSax,
),
SuspiciousMembers::new(
&[
&["xml", "dom", "minidom", "parse"],
&["xml", "dom", "minidom", "parseString"],
],
Reason::XMLMiniDOM,
),
SuspiciousMembers::new(
&[
&["xml", "dom", "pulldom", "parse"],
&["xml", "dom", "pulldom", "parseString"],
],
Reason::XMLPullDOM,
),
SuspiciousMembers::new(
&[
&["lxml", "etree", "parse"],
&["lxml", "etree", "fromstring"],
&["lxml", "etree", "RestrictedElement"],
&["lxml", "etree", "GlobalParserTLS"],
&["lxml", "etree", "getDefaultParser"],
&["lxml", "etree", "check_docinfo"],
],
Reason::XMLETree,
),
];
const SUSPICIOUS_MODULES: &[SuspiciousModule] = &[
SuspiciousModule::new("telnetlib", Reason::Telnet),
SuspiciousModule::new("ftplib", Reason::FTPLib),
];
/// S001
pub fn suspicious_function_call(checker: &mut Checker, expr: &Expr) {
let ExprKind::Call { func, .. } = &expr.node else {
return;
};
let Some(reason) = checker.ctx.resolve_call_path(func).and_then(|call_path| {
for module in SUSPICIOUS_MEMBERS {
for member in module.members {
if call_path.as_slice() == *member {
return Some(module.reason);
}
}
}
for module in SUSPICIOUS_MODULES {
if call_path.first() == Some(&module.name) {
return Some(module.reason);
}
}
None
}) else {
return;
};
let diagnostic_kind = match reason {
Reason::Pickle => SuspiciousPickleUsage.into(),
Reason::Marshal => SuspiciousMarshalUsage.into(),
Reason::InsecureHash => SuspiciousInsecureHashUsage.into(),
Reason::InsecureCipher => SuspiciousInsecureCipherUsage.into(),
Reason::InsecureCipherMode => SuspiciousInsecureCipherModeUsage.into(),
Reason::Mktemp => SuspiciousMktempUsage.into(),
Reason::Eval => SuspiciousEvalUsage.into(),
Reason::MarkSafe => SuspiciousMarkSafeUsage.into(),
Reason::URLOpen => SuspiciousURLOpenUsage.into(),
Reason::NonCryptographicRandom => SuspiciousNonCryptographicRandomUsage.into(),
Reason::XMLCElementTree => SuspiciousXMLCElementTreeUsage.into(),
Reason::XMLElementTree => SuspiciousXMLElementTreeUsage.into(),
Reason::XMLExpatReader => SuspiciousXMLExpatReaderUsage.into(),
Reason::XMLExpatBuilder => SuspiciousXMLExpatBuilderUsage.into(),
Reason::XMLSax => SuspiciousXMLSaxUsage.into(),
Reason::XMLMiniDOM => SuspiciousXMLMiniDOMUsage.into(),
Reason::XMLPullDOM => SuspiciousXMLPullDOMUsage.into(),
Reason::XMLETree => SuspiciousXMLETreeUsage.into(),
Reason::UnverifiedContext => SuspiciousUnverifiedContextUsage.into(),
Reason::Telnet => SuspiciousTelnetUsage.into(),
Reason::FTPLib => SuspiciousFTPLibUsage.into(),
};
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, Range::from(expr));
if checker.settings.rules.enabled(diagnostic.kind.rule()) {
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff/src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
name: SuspiciousPickleUsage
body: "`pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue"
suggestion: ~
fixable: false
location:
row: 3
column: 0
end_location:
row: 3
column: 14
fix: ~
parent: ~

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff/src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
name: SuspiciousTelnetUsage
body: Telnet-related functions are being called. Telnet is considered insecure. Use SSH or some other encrypted protocol.
suggestion: ~
fixable: false
location:
row: 3
column: 0
end_location:
row: 3
column: 23
fix: ~
parent: ~

View File

@@ -20,7 +20,7 @@ mod tests {
#[test_case(Rule::StripWithMultiCharacters, Path::new("B005.py"); "B005")]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"); "B006")]
#[test_case(Rule::UnusedLoopControlVariable, Path::new("B007.py"); "B007")]
#[test_case(Rule::FunctionCallArgumentDefault, Path::new("B006_B008.py"); "B008")]
#[test_case(Rule::FunctionCallInDefaultArgument, Path::new("B006_B008.py"); "B008")]
#[test_case(Rule::GetAttrWithConstant, Path::new("B009_B010.py"); "B009")]
#[test_case(Rule::SetAttrWithConstant, Path::new("B009_B010.py"); "B010")]
#[test_case(Rule::AssertFalse, Path::new("B011.py"); "B011")]
@@ -69,7 +69,7 @@ mod tests {
"fastapi.Query".to_string(),
],
},
..Settings::for_rules(vec![Rule::FunctionCallArgumentDefault])
..Settings::for_rules(vec![Rule::FunctionCallInDefaultArgument])
},
)?;
assert_yaml_snapshot!(snapshot, diagnostics);

View File

@@ -13,14 +13,14 @@ use crate::checkers::ast::Checker;
use super::mutable_argument_default::is_mutable_func;
#[violation]
pub struct FunctionCallArgumentDefault {
pub struct FunctionCallInDefaultArgument {
pub name: Option<String>,
}
impl Violation for FunctionCallArgumentDefault {
impl Violation for FunctionCallInDefaultArgument {
#[derive_message_formats]
fn message(&self) -> String {
let FunctionCallArgumentDefault { name } = self;
let FunctionCallInDefaultArgument { name } = self;
if let Some(name) = name {
format!("Do not perform function call `{name}` in argument defaults")
} else {
@@ -71,7 +71,7 @@ where
&& !is_nan_or_infinity(func, args)
{
self.diagnostics.push((
FunctionCallArgumentDefault {
FunctionCallInDefaultArgument {
name: compose_call_path(func),
}
.into(),

View File

@@ -16,7 +16,7 @@ pub use except_with_non_exception_classes::{
};
pub use f_string_docstring::{f_string_docstring, FStringDocstring};
pub use function_call_argument_default::{
function_call_argument_default, FunctionCallArgumentDefault,
function_call_argument_default, FunctionCallInDefaultArgument,
};
pub use function_uses_loop_variable::{function_uses_loop_variable, FunctionUsesLoopVariable};
pub use getattr_with_constant::{getattr_with_constant, GetAttrWithConstant};

View File

@@ -22,7 +22,7 @@ use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Expr, ExprKind, Stmt};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation};
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, RefEquality};
use ruff_python_ast::visitor::Visitor;
@@ -52,7 +52,7 @@ pub struct UnusedLoopControlVariable {
}
impl Violation for UnusedLoopControlVariable {
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_bugbear/mod.rs
expression: diagnostics
---
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `range` in argument defaults"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `range` in argument defaults"
suggestion: ~
fixable: false
@@ -29,7 +29,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `range` in argument defaults"
suggestion: ~
fixable: false
@@ -42,7 +42,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `time.time` in argument defaults"
suggestion: ~
fixable: false
@@ -55,7 +55,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `dt.datetime.now` in argument defaults"
suggestion: ~
fixable: false
@@ -68,7 +68,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `dt.timedelta` in argument defaults"
suggestion: ~
fixable: false
@@ -81,7 +81,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: Do not perform function call in argument defaults
suggestion: ~
fixable: false
@@ -94,7 +94,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `float` in argument defaults"
suggestion: ~
fixable: false
@@ -107,7 +107,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `float` in argument defaults"
suggestion: ~
fixable: false
@@ -120,7 +120,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `float` in argument defaults"
suggestion: ~
fixable: false
@@ -133,7 +133,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `float` in argument defaults"
suggestion: ~
fixable: false
@@ -146,7 +146,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `dt.datetime.now` in argument defaults"
suggestion: ~
fixable: false
@@ -159,7 +159,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `map` in argument defaults"
suggestion: ~
fixable: false
@@ -172,7 +172,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `random.randint` in argument defaults"
suggestion: ~
fixable: false
@@ -185,7 +185,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `dt.datetime.now` in argument defaults"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_bugbear/mod.rs
expression: diagnostics
---
- kind:
name: FunctionCallArgumentDefault
name: FunctionCallInDefaultArgument
body: "Do not perform function call `Depends` in argument defaults"
suggestion: ~
fixable: false

View File

@@ -19,9 +19,9 @@ mod tests {
let diagnostics = test_path(
Path::new("flake8_commas").join(path).as_path(),
&settings::Settings::for_rules(vec![
Rule::TrailingCommaMissing,
Rule::TrailingCommaOnBareTupleProhibited,
Rule::TrailingCommaProhibited,
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
]),
)?;
assert_yaml_snapshot!(snapshot, diagnostics);

View File

@@ -111,9 +111,9 @@ impl Context {
}
#[violation]
pub struct TrailingCommaMissing;
pub struct MissingTrailingComma;
impl AlwaysAutofixableViolation for TrailingCommaMissing {
impl AlwaysAutofixableViolation for MissingTrailingComma {
#[derive_message_formats]
fn message(&self) -> String {
format!("Trailing comma missing")
@@ -125,9 +125,9 @@ impl AlwaysAutofixableViolation for TrailingCommaMissing {
}
#[violation]
pub struct TrailingCommaOnBareTupleProhibited;
pub struct TrailingCommaOnBareTuple;
impl Violation for TrailingCommaOnBareTupleProhibited {
impl Violation for TrailingCommaOnBareTuple {
#[derive_message_formats]
fn message(&self) -> String {
format!("Trailing comma on bare tuple prohibited")
@@ -135,9 +135,9 @@ impl Violation for TrailingCommaOnBareTupleProhibited {
}
#[violation]
pub struct TrailingCommaProhibited;
pub struct ProhibitedTrailingComma;
impl AlwaysAutofixableViolation for TrailingCommaProhibited {
impl AlwaysAutofixableViolation for ProhibitedTrailingComma {
#[derive_message_formats]
fn message(&self) -> String {
format!("Trailing comma prohibited")
@@ -254,13 +254,13 @@ pub fn trailing_commas(
if comma_prohibited {
let comma = prev.spanned.unwrap();
let mut diagnostic = Diagnostic::new(
TrailingCommaProhibited,
ProhibitedTrailingComma,
Range {
location: comma.0,
end_location: comma.2,
},
);
if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaProhibited) {
if autofix.into() && settings.rules.should_fix(Rule::ProhibitedTrailingComma) {
diagnostic.amend(Fix::deletion(comma.0, comma.2));
}
diagnostics.push(diagnostic);
@@ -273,7 +273,7 @@ pub fn trailing_commas(
if bare_comma_prohibited {
let comma = prev.spanned.unwrap();
diagnostics.push(Diagnostic::new(
TrailingCommaOnBareTupleProhibited,
TrailingCommaOnBareTuple,
Range {
location: comma.0,
end_location: comma.2,
@@ -298,13 +298,13 @@ pub fn trailing_commas(
if comma_required {
let missing_comma = prev_prev.spanned.unwrap();
let mut diagnostic = Diagnostic::new(
TrailingCommaMissing,
MissingTrailingComma,
Range {
location: missing_comma.2,
end_location: missing_comma.2,
},
);
if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaMissing) {
if autofix.into() && settings.rules.should_fix(Rule::MissingTrailingComma) {
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 autofix
// removing any brackets in the same linter pass - doing both at the same time could

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_commas/mod.rs
expression: diagnostics
---
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -23,7 +23,7 @@ expression: diagnostics
column: 17
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -43,7 +43,7 @@ expression: diagnostics
column: 5
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -63,7 +63,7 @@ expression: diagnostics
column: 5
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -83,7 +83,7 @@ expression: diagnostics
column: 5
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -96,7 +96,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -109,7 +109,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -122,7 +122,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -135,7 +135,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -148,7 +148,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -161,7 +161,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaOnBareTupleProhibited
name: TrailingCommaOnBareTuple
body: Trailing comma on bare tuple prohibited
suggestion: ~
fixable: false
@@ -174,7 +174,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -194,7 +194,7 @@ expression: diagnostics
column: 7
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -214,7 +214,7 @@ expression: diagnostics
column: 7
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -234,7 +234,7 @@ expression: diagnostics
column: 7
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -254,7 +254,7 @@ expression: diagnostics
column: 5
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -274,7 +274,7 @@ expression: diagnostics
column: 10
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -294,7 +294,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -314,7 +314,7 @@ expression: diagnostics
column: 13
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -334,7 +334,7 @@ expression: diagnostics
column: 13
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -354,7 +354,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -374,7 +374,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -394,7 +394,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -414,7 +414,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -434,7 +434,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -454,7 +454,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -474,7 +474,7 @@ expression: diagnostics
column: 21
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -494,7 +494,7 @@ expression: diagnostics
column: 13
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -514,7 +514,7 @@ expression: diagnostics
column: 18
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -534,7 +534,7 @@ expression: diagnostics
column: 6
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -554,7 +554,7 @@ expression: diagnostics
column: 21
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -574,7 +574,7 @@ expression: diagnostics
column: 13
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -594,7 +594,7 @@ expression: diagnostics
column: 18
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -614,7 +614,7 @@ expression: diagnostics
column: 6
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -634,7 +634,7 @@ expression: diagnostics
column: 10
parent: ~
- kind:
name: TrailingCommaProhibited
name: ProhibitedTrailingComma
body: Trailing comma prohibited
suggestion: Remove trailing comma
fixable: true
@@ -654,7 +654,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -674,7 +674,7 @@ expression: diagnostics
column: 12
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -694,7 +694,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -714,7 +714,7 @@ expression: diagnostics
column: 15
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -734,7 +734,7 @@ expression: diagnostics
column: 12
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -754,7 +754,7 @@ expression: diagnostics
column: 23
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -774,7 +774,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -794,7 +794,7 @@ expression: diagnostics
column: 12
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -814,7 +814,7 @@ expression: diagnostics
column: 12
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -834,7 +834,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -854,7 +854,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -874,7 +874,7 @@ expression: diagnostics
column: 9
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -894,7 +894,7 @@ expression: diagnostics
column: 12
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -914,7 +914,7 @@ expression: diagnostics
column: 14
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true
@@ -934,7 +934,7 @@ expression: diagnostics
column: 19
parent: ~
- kind:
name: TrailingCommaMissing
name: MissingTrailingComma
body: Trailing comma missing
suggestion: Add trailing comma
fixable: true

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