Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24faabf1f4 | ||
|
|
9b0a160239 | ||
|
|
9fd29e2c54 | ||
|
|
e83ed0ecba | ||
|
|
dadbfea497 | ||
|
|
9f84c497f9 | ||
|
|
0ec25d1514 | ||
|
|
6a87c99004 | ||
|
|
c8f60c9588 | ||
|
|
113610a8d4 | ||
|
|
6376e5915e | ||
|
|
3d8fb5be20 | ||
|
|
0040991778 | ||
|
|
acb70520f8 | ||
|
|
6eb9268675 | ||
|
|
e5f5142e3e | ||
|
|
98d5ffb817 | ||
|
|
3f20f73413 | ||
|
|
a5e42d2f7c | ||
|
|
0bc1f68111 | ||
|
|
d2b09d77c5 | ||
|
|
0377834f9f | ||
|
|
3d650f9dd6 | ||
|
|
a72590ecde | ||
|
|
812b227334 | ||
|
|
6f58717ba4 | ||
|
|
8aab96fb9e | ||
|
|
9e6f7153a9 | ||
|
|
cda2ff0b18 | ||
|
|
ec63658250 | ||
|
|
1a97de0b01 | ||
|
|
1cbe48522e | ||
|
|
bfbde537af | ||
|
|
cba91b758b | ||
|
|
0bab642f5a | ||
|
|
bd09a1819f | ||
|
|
682d206992 | ||
|
|
c32441e4ab | ||
|
|
6f16f1c39b | ||
|
|
9011456aa1 | ||
|
|
fa191cceeb | ||
|
|
ac6c3affdd | ||
|
|
9a018c1650 | ||
|
|
0aef5c67a3 | ||
|
|
a048594416 | ||
|
|
5437f1299b | ||
|
|
41c0608a69 | ||
|
|
eb0d42187f | ||
|
|
48daa0f0ca | ||
|
|
417fe4355f | ||
|
|
a129181407 | ||
|
|
fc628de667 | ||
|
|
9e2418097c | ||
|
|
d4e5639aaf | ||
|
|
67e58a024a | ||
|
|
233be0e074 | ||
|
|
7750087f56 | ||
|
|
7d5fb0de8a | ||
|
|
8a98cfc4b8 | ||
|
|
54d1719424 | ||
|
|
0f622f0126 | ||
|
|
739a92e99d | ||
|
|
5a07c9f57c | ||
|
|
31027497c6 | ||
|
|
dabfdf718e | ||
|
|
5829bae976 | ||
|
|
ff3665a24b |
8
.github/workflows/docs.yaml
vendored
8
.github/workflows/docs.yaml
vendored
@@ -1,12 +1,8 @@
|
||||
name: mkdocs
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- README.md
|
||||
- mkdocs.template.yml
|
||||
- .github/workflows/docs.yaml
|
||||
branches: [main]
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.245
|
||||
|
||||
### Ruff's public `check` method was removed ([#2709](https://github.com/charliermarsh/ruff/pull/2709))
|
||||
|
||||
Previously, Ruff exposed a `check` method as a public Rust API. This method was used by few,
|
||||
if any clients, and was not well documented or supported. As such, it has been removed, with
|
||||
the intention of adding a stable public API in the future.
|
||||
|
||||
## 0.0.238
|
||||
|
||||
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
|
||||
|
||||
@@ -22,6 +22,9 @@ As a concrete example: consider taking on one of the rules from the [`tryceratop
|
||||
plugin, and looking to the originating [Python source](https://github.com/guilatrova/tryceratops)
|
||||
for guidance.
|
||||
|
||||
Alternatively, we've started work on the [`flake8-pyi`](https://github.com/charliermarsh/ruff/issues/848)
|
||||
plugin (see the [Python source](https://github.com/PyCQA/flake8-pyi)) -- another good place to start.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ruff is written in Rust. You'll need to install the
|
||||
@@ -91,15 +94,16 @@ At time of writing, the repository includes the following crates:
|
||||
|
||||
At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
|
||||
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
|
||||
2. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
|
||||
3. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
|
||||
4. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
|
||||
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention).
|
||||
2. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
|
||||
3. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
|
||||
4. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
|
||||
5. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
|
||||
checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks), `crates/ruff/src/checkers/lines.rs`
|
||||
(for text-based checks), or `crates/ruff/src/checkers/filesystem.rs` (for filesystem-based
|
||||
checks).
|
||||
5. Add a test fixture.
|
||||
6. Update the generated files (documentation and generated code).
|
||||
6. Add a test fixture.
|
||||
7. Update the generated files (documentation and generated code).
|
||||
|
||||
To define the violation, start by creating a dedicated file for your rule under the appropriate
|
||||
rule linter (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
|
||||
@@ -129,6 +133,17 @@ generated snapshot, then commit the snapshot file alongside the rest of your cha
|
||||
|
||||
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
|
||||
|
||||
#### Rule naming convention
|
||||
|
||||
The rule name should make sense when read as "allow *rule-name*" or "allow *rule-name* items".
|
||||
|
||||
This implies that rule names:
|
||||
|
||||
* should state the bad thing being checked for
|
||||
|
||||
* should not contain instructions on what you what you should use instead
|
||||
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
Ruff's user-facing settings live in a few different places.
|
||||
|
||||
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -747,7 +747,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.1.4",
|
||||
@@ -1896,7 +1896,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bisection",
|
||||
@@ -1952,7 +1952,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1988,7 +1988,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.1.4",
|
||||
@@ -2008,10 +2008,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -2020,7 +2019,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"regex",
|
||||
@@ -2072,7 +2071,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=d94d0ac72072eb60bd9363e69b96ff1d5eb401b3#d94d0ac72072eb60bd9363e69b96ff1d5eb401b3"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -2081,7 +2080,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=d94d0ac72072eb60bd9363e69b96ff1d5eb401b3#d94d0ac72072eb60bd9363e69b96ff1d5eb401b3"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"bitflags",
|
||||
@@ -2106,7 +2105,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=d94d0ac72072eb60bd9363e69b96ff1d5eb401b3#d94d0ac72072eb60bd9363e69b96ff1d5eb401b3"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2123,7 +2122,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=d94d0ac72072eb60bd9363e69b96ff1d5eb401b3#d94d0ac72072eb60bd9363e69b96ff1d5eb401b3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
members = ["crates/*"]
|
||||
default-members = ["crates/ruff", "crates/ruff_cli"]
|
||||
|
||||
[workspace.dependencies]
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "d94d0ac72072eb60bd9363e69b96ff1d5eb401b3" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "d94d0ac72072eb60bd9363e69b96ff1d5eb401b3" }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -245,6 +245,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-pyi, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Łukasz Langa
|
||||
|
||||
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-print, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
138
README.md
138
README.md
@@ -145,6 +145,7 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
|
||||
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
|
||||
1. [flake8-pie (PIE)](#flake8-pie-pie)
|
||||
1. [flake8-print (T20)](#flake8-print-t20)
|
||||
1. [flake8-pyi (PYI)](#flake8-pyi-pyi)
|
||||
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
|
||||
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
||||
1. [flake8-return (RET)](#flake8-return-ret)
|
||||
@@ -230,7 +231,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.244'
|
||||
rev: 'v0.0.245'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -456,14 +457,6 @@ File selection:
|
||||
--respect-gitignore Respect file exclusions via `.gitignore` and other standard ignore files
|
||||
--force-exclude Enforce exclusions, even for paths passed to Ruff directly on the command-line
|
||||
|
||||
Rule configuration:
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
--line-length <LINE_LENGTH>
|
||||
Set the line-length for length-associated rules and automatic formatting
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
Regular expression matching the name of dummy variables
|
||||
|
||||
Miscellaneous:
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
@@ -631,17 +624,17 @@ for more.
|
||||
|
||||
By default, Ruff exits with the following status codes:
|
||||
|
||||
- `0` if no violations were found, or if all present violations were fixed automatically.
|
||||
- `1` if violations were found.
|
||||
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an internal error.
|
||||
* `0` if no violations were found, or if all present violations were fixed automatically.
|
||||
* `1` if violations were found.
|
||||
* `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an internal error.
|
||||
|
||||
This convention mirrors that of tools like ESLint, Prettier, and RuboCop.
|
||||
|
||||
Ruff supports two command-line flags that alter its exit code behavior:
|
||||
|
||||
- `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found.
|
||||
* `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found.
|
||||
Note that Ruff will still exit with a status code of `2` if it terminates abnormally.
|
||||
- `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were
|
||||
* `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were
|
||||
found, _even if_ all such violations were fixed automatically. Note that the use of
|
||||
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
|
||||
autofixing.
|
||||
@@ -671,7 +664,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
|
||||
| ---- | ---- | ------- | --- |
|
||||
| F401 | unused-import | `{name}` imported but unused; consider adding to `__all__` or using a redundant alias | 🛠 |
|
||||
| F402 | import-shadowed-by-loop-var | Import `{name}` from line {line} shadowed by loop variable | |
|
||||
| F403 | import-star-used | `from {name} import *` used; unable to detect undefined names | |
|
||||
| F403 | import-star | `from {name} import *` used; unable to detect undefined names | |
|
||||
| F404 | late-future-import | `from __future__` imports must occur at the beginning of the file | |
|
||||
| F405 | import-star-usage | `{name}` may be undefined, or defined from star imports: {sources} | |
|
||||
| F406 | import-star-not-permitted | `from {name} import *` only allowed at module level | |
|
||||
@@ -725,13 +718,17 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/) on PyPI.
|
||||
| E401 | multiple-imports-on-one-line | Multiple imports on one line | |
|
||||
| E402 | module-import-not-at-top-of-file | Module level import not at top of file | |
|
||||
| E501 | line-too-long | Line too long ({length} > {limit} characters) | |
|
||||
| E701 | multiple-statements-on-one-line-colon | Multiple statements on one line (colon) | |
|
||||
| E702 | multiple-statements-on-one-line-semicolon | Multiple statements on one line (semicolon) | |
|
||||
| E703 | useless-semicolon | Statement ends with an unnecessary semicolon | |
|
||||
| E704 | multiple-statements-on-one-line-def | Multiple statements on one line (def) | |
|
||||
| E711 | none-comparison | Comparison to `None` should be `cond is None` | 🛠 |
|
||||
| E712 | true-false-comparison | Comparison to `True` should be `cond is True` | 🛠 |
|
||||
| E713 | not-in-test | Test for membership should be `not in` | 🛠 |
|
||||
| E714 | not-is-test | Test for object identity should be `is not` | 🛠 |
|
||||
| E721 | type-comparison | Do not compare types, use `isinstance()` | |
|
||||
| E722 | do-not-use-bare-except | Do not use bare `except` | |
|
||||
| E731 | do-not-assign-lambda | Do not assign a `lambda` expression, use a `def` | 🛠 |
|
||||
| E722 | bare-except | Do not use bare `except` | |
|
||||
| E731 | lambda-assignment | Do not assign a `lambda` expression, use a `def` | 🛠 |
|
||||
| E741 | ambiguous-variable-name | Ambiguous variable name: `{name}` | |
|
||||
| E742 | ambiguous-class-name | Ambiguous class name: `{name}` | |
|
||||
| E743 | ambiguous-function-name | Ambiguous function name: `{name}` | |
|
||||
@@ -752,7 +749,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C901 | function-is-too-complex | `{name}` is too complex ({complexity}) | |
|
||||
| C901 | [function-is-too-complex](https://github.com/charliermarsh/ruff/blob/main/docs/rules/function-is-too-complex.md) | `{name}` is too complex ({complexity}) | |
|
||||
|
||||
### isort (I)
|
||||
|
||||
@@ -760,8 +757,8 @@ For more, see [isort](https://pypi.org/project/isort/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| I001 | unsorted-imports | Import block is un-sorted or un-formatted | 🛠 |
|
||||
| I002 | missing-required-import | Missing required import: `{name}` | 🛠 |
|
||||
| I001 | [unsorted-imports](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unsorted-imports.md) | Import block is un-sorted or un-formatted | 🛠 |
|
||||
| I002 | [missing-required-import](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-required-import.md) | Missing required import: `{name}` | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -815,8 +812,8 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
|
||||
| D213 | multi-line-summary-second-line | Multi-line docstring summary should start at the second line | 🛠 |
|
||||
| D214 | section-not-over-indented | Section is over-indented ("{name}") | 🛠 |
|
||||
| D215 | section-underline-not-over-indented | Section underline is over-indented ("{name}") | 🛠 |
|
||||
| D300 | uses-triple-quotes | Use """triple double quotes""" | |
|
||||
| D301 | uses-r-prefix-for-backslashed-content | Use r""" if any backslashes in a docstring | |
|
||||
| D300 | triple-single-quotes | Use """triple double quotes""" | |
|
||||
| D301 | escape-sequence-in-docstring | Use r""" if any backslashes in a docstring | |
|
||||
| D400 | ends-in-period | First line should end with a period | 🛠 |
|
||||
| D401 | non-imperative-mood | First line of docstring should be in imperative mood: "{first_line}" | |
|
||||
| D402 | no-signature | First line should not be the function's signature | |
|
||||
@@ -903,17 +900,17 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| ANN001 | missing-type-function-argument | Missing type annotation for function argument `{name}` | |
|
||||
| ANN002 | missing-type-args | Missing type annotation for `*{name}` | |
|
||||
| ANN003 | missing-type-kwargs | Missing type annotation for `**{name}` | |
|
||||
| ANN101 | missing-type-self | Missing type annotation for `{name}` in method | |
|
||||
| ANN102 | missing-type-cls | Missing type annotation for `{name}` in classmethod | |
|
||||
| ANN201 | missing-return-type-public-function | Missing return type annotation for public function `{name}` | |
|
||||
| ANN202 | missing-return-type-private-function | Missing return type annotation for private function `{name}` | |
|
||||
| ANN204 | missing-return-type-special-method | Missing return type annotation for special method `{name}` | 🛠 |
|
||||
| ANN205 | missing-return-type-static-method | Missing return type annotation for staticmethod `{name}` | |
|
||||
| ANN206 | missing-return-type-class-method | Missing return type annotation for classmethod `{name}` | |
|
||||
| ANN401 | dynamically-typed-expression | Dynamically typed expressions (typing.Any) are disallowed in `{name}` | |
|
||||
| ANN001 | [missing-type-function-argument](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-function-argument.md) | Missing type annotation for function argument `{name}` | |
|
||||
| ANN002 | [missing-type-args](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-args.md) | Missing type annotation for `*{name}` | |
|
||||
| ANN003 | [missing-type-kwargs](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-kwargs.md) | Missing type annotation for `**{name}` | |
|
||||
| ANN101 | [missing-type-self](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-self.md) | Missing type annotation for `{name}` in method | |
|
||||
| ANN102 | [missing-type-cls](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-cls.md) | Missing type annotation for `{name}` in classmethod | |
|
||||
| ANN201 | [missing-return-type-public-function](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-public-function.md) | Missing return type annotation for public function `{name}` | |
|
||||
| ANN202 | [missing-return-type-private-function](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-private-function.md) | Missing return type annotation for private function `{name}` | |
|
||||
| ANN204 | [missing-return-type-special-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-special-method.md) | Missing return type annotation for special method `{name}` | 🛠 |
|
||||
| ANN205 | [missing-return-type-static-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-static-method.md) | Missing return type annotation for staticmethod `{name}` | |
|
||||
| ANN206 | [missing-return-type-class-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-class-method.md) | Missing return type annotation for classmethod `{name}` | |
|
||||
| ANN401 | [any-type](https://github.com/charliermarsh/ruff/blob/main/docs/rules/any-type.md) | Dynamically typed expressions (typing.Any) are disallowed in `{name}` | |
|
||||
|
||||
### flake8-bandit (S)
|
||||
|
||||
@@ -921,8 +918,8 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| S101 | assert-used | Use of `assert` detected | |
|
||||
| S102 | exec-used | Use of `exec` detected | |
|
||||
| S101 | assert | Use of `assert` detected | |
|
||||
| S102 | exec-builtin | Use of `exec` detected | |
|
||||
| S103 | bad-file-permissions | `os.chmod` setting a permissive mask `{mask:#o}` on file or directory | |
|
||||
| S104 | hardcoded-bind-all-interfaces | Possible binding to all interfaces | |
|
||||
| S105 | hardcoded-password-string | Possible hardcoded password: "{}" | |
|
||||
@@ -930,12 +927,14 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
|
||||
| S107 | hardcoded-password-default | Possible hardcoded password: "{}" | |
|
||||
| S108 | hardcoded-temp-file | Probable insecure usage of temporary file or directory: "{}" | |
|
||||
| S110 | try-except-pass | `try`-`except`-`pass` detected, consider logging the exception | |
|
||||
| S112 | try-except-continue | `try`-`except`-`continue` detected, consider logging the exception | |
|
||||
| S113 | request-without-timeout | Probable use of requests call with timeout set to `{value}` | |
|
||||
| S324 | hashlib-insecure-hash-function | Probable use of insecure hash functions in `hashlib`: "{}" | |
|
||||
| S501 | request-with-no-cert-validation | Probable use of `{string}` call with `verify=False` disabling SSL certificate checks | |
|
||||
| S506 | unsafe-yaml-load | Probable use of unsafe loader `{name}` with `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`. | |
|
||||
| S508 | snmp-insecure-version | The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. | |
|
||||
| S509 | snmp-weak-cryptography | You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. | |
|
||||
| S608 | [hardcoded-sql-expression](https://github.com/charliermarsh/ruff/blob/main/docs/rules/hardcoded-sql-expression.md) | Possible SQL injection vector through string-based query construction: "{}" | |
|
||||
| S612 | logging-config-insecure-listen | Use of insecure `logging.config.listen` detected | |
|
||||
| S701 | jinja2-autoescape-false | Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function. | |
|
||||
|
||||
@@ -972,13 +971,13 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) on PyPI
|
||||
| B008 | function-call-argument-default | Do not perform function call `{name}` in argument defaults | |
|
||||
| B009 | get-attr-with-constant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B010 | set-attr-with-constant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B011 | do-not-assert-false | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B011 | assert-false | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B012 | jump-statement-in-finally | `{name}` inside `finally` blocks cause exceptions to be silenced | |
|
||||
| B013 | redundant-tuple-in-exception-handler | A length-one tuple literal is redundant. Write `except {name}` instead of `except ({name},)`. | 🛠 |
|
||||
| B014 | duplicate-handler-exception | Exception handler with duplicate exception: `{name}` | 🛠 |
|
||||
| B015 | useless-comparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
|
||||
| B016 | cannot-raise-literal | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
|
||||
| [B017](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | [assert-raises-exception](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | `assertRaises(Exception)` should be considered evil | |
|
||||
| B017 | [assert-raises-exception](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | `assertRaises(Exception)` should be considered evil | |
|
||||
| B018 | useless-expression | Found useless expression. Either assign it to a variable or remove it. | |
|
||||
| B019 | cached-instance-method | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks | |
|
||||
| B020 | loop-variable-overrides-iterator | Loop control variable `{name}` overrides iterable it iterates | |
|
||||
@@ -1097,7 +1096,7 @@ For more, see [flake8-import-conventions](https://github.com/joaopalmeiro/flake8
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| [ICN001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | [unconventional-import-alias](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | `{name}` should be imported as `{asname}` | |
|
||||
| ICN001 | [unconventional-import-alias](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | `{name}` should be imported as `{asname}` | |
|
||||
|
||||
### flake8-logging-format (G)
|
||||
|
||||
@@ -1120,7 +1119,7 @@ For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/) on
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| [INP001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | [implicit-namespace-package](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
|
||||
| INP001 | [implicit-namespace-package](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
|
||||
|
||||
### flake8-pie (PIE)
|
||||
|
||||
@@ -1145,6 +1144,14 @@ For more, see [flake8-print](https://pypi.org/project/flake8-print/) on PyPI.
|
||||
| T201 | print-found | `print` found | |
|
||||
| T203 | p-print-found | `pprint` found | |
|
||||
|
||||
### flake8-pyi (PYI)
|
||||
|
||||
For more, see [flake8-pyi](https://pypi.org/project/flake8-pyi/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PYI001 | [prefix-type-params](https://github.com/charliermarsh/ruff/blob/main/docs/rules/prefix-type-params.md) | Name of private `{kind}` must start with _ | |
|
||||
|
||||
### flake8-pytest-style (PT)
|
||||
|
||||
For more, see [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/) on PyPI.
|
||||
@@ -1183,10 +1190,10 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| [Q000](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | [bad-quotes-inline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | Double quotes found but single quotes preferred | 🛠 |
|
||||
| [Q001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | [bad-quotes-multiline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | Double quote multiline found but single quotes preferred | 🛠 |
|
||||
| [Q002](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | [bad-quotes-docstring](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | Double quote docstring found but single quotes preferred | 🛠 |
|
||||
| [Q003](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoid-quote-escape.md) | [avoid-quote-escape](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoid-quote-escape.md) | Change outer quotes to avoid escaping inner quotes | 🛠 |
|
||||
| Q000 | [bad-quotes-inline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | Double quotes found but single quotes preferred | 🛠 |
|
||||
| Q001 | [bad-quotes-multiline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | Double quote multiline found but single quotes preferred | 🛠 |
|
||||
| Q002 | [bad-quotes-docstring](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | Double quote docstring found but single quotes preferred | 🛠 |
|
||||
| Q003 | [avoidable-escaped-quote](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoidable-escaped-quote.md) | Change outer quotes to avoid escaping inner quotes | 🛠 |
|
||||
|
||||
### flake8-return (RET)
|
||||
|
||||
@@ -1211,7 +1218,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on Py
|
||||
| ---- | ---- | ------- | --- |
|
||||
| SIM101 | duplicate-isinstance-call | Multiple `isinstance` calls for `{name}`, merge into a single call | 🛠 |
|
||||
| SIM102 | nested-if-statements | Use a single `if` statement instead of nested `if` statements | 🛠 |
|
||||
| SIM103 | return-bool-condition-directly | Return the condition `{cond}` directly | 🛠 |
|
||||
| SIM103 | return-bool-condition-directly | Return the condition `{condition}` directly | 🛠 |
|
||||
| SIM105 | use-contextlib-suppress | Use `contextlib.suppress({exception})` instead of try-except-pass | |
|
||||
| SIM107 | return-in-try-except-finally | Don't use `return` in `try`/`except` and `finally` | |
|
||||
| SIM108 | use-ternary-operator | Use ternary operator `{contents}` instead of if-else-block | 🛠 |
|
||||
@@ -1242,7 +1249,7 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| TID251 | banned-api | `{name}` is banned: {message} | |
|
||||
| TID252 | relative-imports | Relative imports from parent modules are banned | |
|
||||
| TID252 | [relative-imports](https://github.com/charliermarsh/ruff/blob/main/docs/rules/relative-imports.md) | Relative imports from parent modules are banned | 🛠 |
|
||||
|
||||
### flake8-type-checking (TCH)
|
||||
|
||||
@@ -1306,7 +1313,7 @@ For more, see [eradicate](https://pypi.org/project/eradicate/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| [ERA001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | [commented-out-code](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | Found commented-out code | 🛠 |
|
||||
| ERA001 | [commented-out-code](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | Found commented-out code | 🛠 |
|
||||
|
||||
### pandas-vet (PD)
|
||||
|
||||
@@ -1353,13 +1360,15 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PLE0100 | [yield-in-init](https://github.com/charliermarsh/ruff/blob/main/docs/rules/yield-in-init.md) | `__init__` method is a generator | |
|
||||
| PLE0117 | nonlocal-without-binding | Nonlocal name `{name}` found without binding | |
|
||||
| PLE0118 | used-prior-global-declaration | Name `{name}` is used prior to global declaration on line {line} | |
|
||||
| PLE0604 | invalid-all-object | Invalid object in `__all__`, must contain only strings | |
|
||||
| PLE0605 | invalid-all-format | Invalid format for `__all__`, must be `tuple` or `list` | |
|
||||
| PLE1142 | await-outside-async | `await` should be used within an async function | |
|
||||
| PLE1307 | [bad-string-format-type](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-string-format-type.md) | Format type does not match argument type | |
|
||||
| PLE1310 | bad-str-strip-call | String `{strip}` call contains duplicate characters (did you mean `{removal}`?) | |
|
||||
| PLE2502 | bidirectional-unicode | Avoid using bidirectional unicode | |
|
||||
| PLE2502 | bidirectional-unicode | Contains control characters that can permit obfuscated code | |
|
||||
|
||||
#### Refactor (PLR)
|
||||
|
||||
@@ -1389,7 +1398,7 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| [TRY002](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | [raise-vanilla-class](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | Create your own exception | |
|
||||
| TRY002 | [raise-vanilla-class](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | Create your own exception | |
|
||||
| TRY003 | raise-vanilla-args | Avoid specifying long messages outside the exception class | |
|
||||
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 |
|
||||
| TRY200 | reraise-no-cause | Use `raise from` to specify exception cause | |
|
||||
@@ -1422,7 +1431,7 @@ For more, see [flake8-self](https://pypi.org/project/flake8-self/) on PyPI.
|
||||
| RUF002 | ambiguous-unicode-character-docstring | Docstring contains ambiguous unicode character '{confusable}' (did you mean '{representant}'?) | 🛠 |
|
||||
| RUF003 | ambiguous-unicode-character-comment | Comment contains ambiguous unicode character '{confusable}' (did you mean '{representant}'?) | 🛠 |
|
||||
| RUF004 | keyword-argument-before-star-argument | Keyword argument `{name}` must come after starred arguments | |
|
||||
| RUF005 | unpack-instead-of-concatenating-to-collection-literal | Consider `{expr}` instead of concatenation | |
|
||||
| RUF005 | unpack-instead-of-concatenating-to-collection-literal | Consider `{expr}` instead of concatenation | 🛠 |
|
||||
| RUF100 | unused-noqa | Unused blanket `noqa` directive | 🛠 |
|
||||
|
||||
<!-- End auto-generated sections. -->
|
||||
@@ -1704,6 +1713,7 @@ natively, including:
|
||||
* [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/)
|
||||
@@ -1975,6 +1985,15 @@ unfixable = ["B", "SIM", "TRY", "RUF"]
|
||||
|
||||
If you find a case where Ruff's autofix breaks your code, please file an Issue!
|
||||
|
||||
### How can I disable Ruff's color output?
|
||||
|
||||
Ruff's color output is powered by the [`colored`](https://crates.io/crates/colored) crate, which
|
||||
attempts to automatically detect whether the output stream supports color. However, you can force
|
||||
colors off by setting the `NO_COLOR` environment variable to any value (e.g., `NO_COLOR=1`).
|
||||
|
||||
[`colored`](https://crates.io/crates/colored) also supports the the `CLICOLOR` and `CLICOLOR_FORCE`
|
||||
environment variables (see the [spec](https://bixense.com/clicolors/)).
|
||||
|
||||
<!-- End section: FAQ -->
|
||||
|
||||
## Contributing
|
||||
@@ -3396,7 +3415,7 @@ alias (e.g., `import A as B`) to wrap such that every line contains
|
||||
exactly one member. For example, this formatting would be retained,
|
||||
rather than condensing to a single line:
|
||||
|
||||
```py
|
||||
```python
|
||||
from .utils import (
|
||||
test_directory as test_directory,
|
||||
test_id as test_id
|
||||
@@ -3458,6 +3477,25 @@ known-first-party = ["src"]
|
||||
|
||||
---
|
||||
|
||||
#### [`known-local-folder`](#known-local-folder)
|
||||
|
||||
A list of modules to consider being a local folder.
|
||||
Generally, this is reserved for relative
|
||||
imports (from . import module).
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
known-local-folder = ["src"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`known-third-party`](#known-third-party)
|
||||
|
||||
A list of modules to consider third-party, regardless of whether they
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.244"
|
||||
version = "0.0.245"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -30,7 +30,7 @@ globset = { version = "0.4.9" }
|
||||
ignore = { version = "0.4.18" }
|
||||
imperative = { version = "1.0.3" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
libcst = { workspace = true }
|
||||
log = { version = "0.4.17" }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
@@ -39,11 +39,11 @@ num-traits = "0.2.15"
|
||||
once_cell = { version = "1.16.0" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff_macros = { version = "0.0.244", path = "../ruff_macros" }
|
||||
ruff_python = { version = "0.0.244", path = "../ruff_python" }
|
||||
ruff_macros = { version = "0.0.245", path = "../ruff_macros" }
|
||||
ruff_python = { version = "0.0.245", path = "../ruff_python" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
schemars = { version = "0.8.11" }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
|
||||
4
crates/ruff/resources/test/disallowed_rule_names.txt
Normal file
4
crates/ruff/resources/test/disallowed_rule_names.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
avoid-*
|
||||
do-not-*
|
||||
uses-*
|
||||
*-used
|
||||
29
crates/ruff/resources/test/fixtures/flake8_bandit/S112.py
vendored
Normal file
29
crates/ruff/resources/test/fixtures/flake8_bandit/S112.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception,):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception, ValueError):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
continue
|
||||
95
crates/ruff/resources/test/fixtures/flake8_bandit/S608.py
vendored
Normal file
95
crates/ruff/resources/test/fixtures/flake8_bandit/S608.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# single-line failures
|
||||
query1 = "SELECT %s FROM table" % (var,) # bad
|
||||
query2 = "SELECT var FROM " + table
|
||||
query3 = "SELECT " + val + " FROM " + table
|
||||
query4 = "SELECT {} FROM table;".format(var)
|
||||
query5 = f"SELECT * FROM table WHERE var = {var}"
|
||||
|
||||
query6 = "DELETE FROM table WHERE var = %s" % (var,)
|
||||
query7 = "DELETE FROM table WHERE VAR = " + var
|
||||
query8 = "DELETE FROM " + table + "WHERE var = " + var
|
||||
query9 = "DELETE FROM table WHERE var = {}".format(var)
|
||||
query10 = f"DELETE FROM table WHERE var = {var}"
|
||||
|
||||
query11 = "INSERT INTO table VALUES (%s)" % (var,)
|
||||
query12 = "INSERT INTO TABLE VALUES (" + var + ")"
|
||||
query13 = "INSERT INTO {} VALUES ({})".format(table, var)
|
||||
query14 = f"INSERT INTO {table} VALUES var = {var}"
|
||||
|
||||
query15 = "UPDATE %s SET var = %s" % (table, var)
|
||||
query16 = "UPDATE " + table + " SET var = " + var
|
||||
query17 = "UPDATE {} SET var = {}".format(table, var)
|
||||
query18 = f"UPDATE {table} SET var = {var}"
|
||||
|
||||
query19 = "select %s from table" % (var,)
|
||||
query20 = "select var from " + table
|
||||
query21 = "select " + val + " from " + table
|
||||
query22 = "select {} from table;".format(var)
|
||||
query23 = f"select * from table where var = {var}"
|
||||
|
||||
query24 = "delete from table where var = %s" % (var,)
|
||||
query25 = "delete from table where var = " + var
|
||||
query26 = "delete from " + table + "where var = " + var
|
||||
query27 = "delete from table where var = {}".format(var)
|
||||
query28 = f"delete from table where var = {var}"
|
||||
|
||||
query29 = "insert into table values (%s)" % (var,)
|
||||
query30 = "insert into table values (" + var + ")"
|
||||
query31 = "insert into {} values ({})".format(table, var)
|
||||
query32 = f"insert into {table} values var = {var}"
|
||||
|
||||
query33 = "update %s set var = %s" % (table, var)
|
||||
query34 = "update " + table + " set var = " + var
|
||||
query35 = "update {} set var = {}".format(table, var)
|
||||
query36 = f"update {table} set var = {var}"
|
||||
|
||||
# multi-line failures
|
||||
def query37():
|
||||
return """
|
||||
SELECT *
|
||||
FROM table
|
||||
WHERE var = %s
|
||||
""" % var
|
||||
|
||||
def query38():
|
||||
return """
|
||||
SELECT *
|
||||
FROM TABLE
|
||||
WHERE var =
|
||||
""" + var
|
||||
|
||||
def query39():
|
||||
return """
|
||||
SELECT *
|
||||
FROM table
|
||||
WHERE var = {}
|
||||
""".format(var)
|
||||
|
||||
def query40():
|
||||
return f"""
|
||||
SELECT *
|
||||
FROM table
|
||||
WHERE var = {var}
|
||||
"""
|
||||
|
||||
def query41():
|
||||
return (
|
||||
"SELECT *"
|
||||
"FROM table"
|
||||
f"WHERE var = {var}"
|
||||
)
|
||||
|
||||
# # cursor-wrapped failures
|
||||
query42 = cursor.execute("SELECT * FROM table WHERE var = %s" % var)
|
||||
query43 = cursor.execute(f"SELECT * FROM table WHERE var = {var}")
|
||||
query44 = cursor.execute("SELECT * FROM table WHERE var = {}".format(var))
|
||||
query45 = cursor.executemany("SELECT * FROM table WHERE var = %s" % var, [])
|
||||
|
||||
# # pass
|
||||
query = "SELECT * FROM table WHERE id = 1"
|
||||
query = "DELETE FROM table WHERE id = 1"
|
||||
query = "INSERT INTO table VALUES (1)"
|
||||
query = "UPDATE table SET id = 1"
|
||||
cursor.execute('SELECT * FROM table WHERE id = %s', var)
|
||||
cursor.execute('SELECT * FROM table WHERE id = 1')
|
||||
cursor.executemany('SELECT * FROM table WHERE id = %s', [var, var2])
|
||||
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI001.py
vendored
Normal file
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI001.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from typing import ParamSpec, TypeVar, TypeVarTuple
|
||||
|
||||
T = TypeVar("T") # OK
|
||||
|
||||
TTuple = TypeVarTuple("TTuple") # OK
|
||||
|
||||
P = ParamSpec("P") # OK
|
||||
|
||||
_T = TypeVar("_T") # OK
|
||||
|
||||
_TTuple = TypeVarTuple("_TTuple") # OK
|
||||
|
||||
_P = ParamSpec("_P") # OK
|
||||
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI001.pyi
vendored
Normal file
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI001.pyi
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from typing import ParamSpec, TypeVar, TypeVarTuple
|
||||
|
||||
T = TypeVar("T") # Error: TypeVars in stubs must start with _
|
||||
|
||||
TTuple = TypeVarTuple("TTuple") # Error: TypeVarTuples must also start with _
|
||||
|
||||
P = ParamSpec("P") # Error: ParamSpecs must start with _
|
||||
|
||||
_T = TypeVar("_T") # OK
|
||||
|
||||
_TTuple = TypeVarTuple("_TTuple") # OK
|
||||
|
||||
_P = ParamSpec("_P") # OK
|
||||
@@ -1,3 +1,14 @@
|
||||
import builtins
|
||||
import os
|
||||
import posix
|
||||
from posix import abort
|
||||
import sys as std_sys
|
||||
import _thread
|
||||
import _winapi
|
||||
|
||||
import pytest
|
||||
from pytest import xfail as py_xfail
|
||||
|
||||
###
|
||||
# Errors
|
||||
###
|
||||
@@ -39,6 +50,20 @@ def x(y):
|
||||
print() # error
|
||||
|
||||
|
||||
# A nonexistent function
|
||||
def func_unknown(x):
|
||||
if x > 0:
|
||||
return False
|
||||
no_such_function() # error
|
||||
|
||||
|
||||
# A function that does return the control
|
||||
def func_no_noreturn(x):
|
||||
if x > 0:
|
||||
return False
|
||||
print("", end="") # error
|
||||
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
@@ -123,3 +148,106 @@ def prompts(self, foo):
|
||||
for x in foo:
|
||||
yield x
|
||||
yield x + 1
|
||||
|
||||
|
||||
# Functions that never return
|
||||
def noreturn_exit(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
exit()
|
||||
|
||||
|
||||
def noreturn_quit(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
quit()
|
||||
|
||||
|
||||
def noreturn_builtins_exit(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
builtins.exit()
|
||||
|
||||
|
||||
def noreturn_builtins_quit(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
builtins.quit()
|
||||
|
||||
|
||||
def noreturn_os__exit(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
os._exit(0)
|
||||
|
||||
|
||||
def noreturn_os_abort(x):
|
||||
if x > 0:
|
||||
return 1
|
||||
os.abort()
|
||||
|
||||
|
||||
def noreturn_posix__exit():
|
||||
if x > 0:
|
||||
return 1
|
||||
posix._exit()
|
||||
|
||||
|
||||
def noreturn_posix_abort():
|
||||
if x > 0:
|
||||
return 1
|
||||
posix.abort()
|
||||
|
||||
|
||||
def noreturn_posix_abort_2():
|
||||
if x > 0:
|
||||
return 1
|
||||
abort()
|
||||
|
||||
|
||||
def noreturn_sys_exit():
|
||||
if x > 0:
|
||||
return 1
|
||||
std_sys.exit(0)
|
||||
|
||||
|
||||
def noreturn__thread_exit():
|
||||
if x > 0:
|
||||
return 1
|
||||
_thread.exit(0)
|
||||
|
||||
|
||||
def noreturn__winapi_exitprocess():
|
||||
if x > 0:
|
||||
return 1
|
||||
_winapi.ExitProcess(0)
|
||||
|
||||
|
||||
def noreturn_pytest_exit():
|
||||
if x > 0:
|
||||
return 1
|
||||
pytest.exit("oof")
|
||||
|
||||
|
||||
def noreturn_pytest_fail():
|
||||
if x > 0:
|
||||
return 1
|
||||
pytest.fail("oof")
|
||||
|
||||
|
||||
def noreturn_pytest_skip():
|
||||
if x > 0:
|
||||
return 1
|
||||
pytest.skip("oof")
|
||||
|
||||
|
||||
def noreturn_pytest_xfail():
|
||||
if x > 0:
|
||||
return 1
|
||||
pytest.xfail("oof")
|
||||
|
||||
|
||||
def noreturn_pytest_xfail_2():
|
||||
if x > 0:
|
||||
return 1
|
||||
py_xfail("oof")
|
||||
|
||||
@@ -33,6 +33,8 @@ class Foo(metaclass=BazMeta):
|
||||
def get_bar():
|
||||
if self.bar._private: # SLF001
|
||||
return None
|
||||
if self.bar()._private: # SLF001
|
||||
return None
|
||||
return self.bar
|
||||
|
||||
def public_func(self):
|
||||
@@ -51,9 +53,11 @@ print(foo.public_thing)
|
||||
print(foo.public_func())
|
||||
print(foo.__dict__)
|
||||
print(foo.__str__())
|
||||
print(foo().__class__)
|
||||
|
||||
print(foo._private_thing) # SLF001
|
||||
print(foo.__really_private_thing) # SLF001
|
||||
print(foo._private_func()) # SLF001
|
||||
print(foo.__really_private_func(1)) # SLF001
|
||||
print(foo.bar._private) # SLF001
|
||||
print(foo()._private_thing) # SLF001
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
from . import sibling
|
||||
from .sibling import example
|
||||
|
||||
from .. import parent
|
||||
from ..parent import example
|
||||
|
||||
from ... import grandparent
|
||||
from ...grandparent import example
|
||||
|
||||
# OK
|
||||
import other
|
||||
import other.example
|
||||
from other import example
|
||||
|
||||
# TID252
|
||||
from . import sibling
|
||||
from .sibling import example
|
||||
from .. import parent
|
||||
from ..parent import example
|
||||
from ... import grandparent
|
||||
from ...grandparent import example
|
||||
from .parent import hello
|
||||
from .\
|
||||
parent import \
|
||||
hello_world
|
||||
from \
|
||||
..parent\
|
||||
import \
|
||||
world_hello
|
||||
|
||||
# TID252 (without autofix; too many levels up)
|
||||
from ..... import ultragrantparent
|
||||
from ...... import ultragrantparent
|
||||
from ....... import ultragrantparent
|
||||
from ......... import ultragrantparent
|
||||
from ........................... import ultragrantparent
|
||||
from .....parent import ultragrantparent
|
||||
from .........parent import ultragrantparent
|
||||
from ...........................parent import ultragrantparent
|
||||
|
||||
6
crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_11.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_11.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import List
|
||||
|
||||
__all__ = ("List",)
|
||||
@@ -3,3 +3,4 @@ line-length = 88
|
||||
|
||||
[tool.ruff.isort]
|
||||
lines-after-imports = 3
|
||||
known-local-folder = ["ruff"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import ruff
|
||||
import leading_prefix
|
||||
import os
|
||||
from . import leading_prefix
|
||||
|
||||
46
crates/ruff/resources/test/fixtures/pycodestyle/E70.py
vendored
Normal file
46
crates/ruff/resources/test/fixtures/pycodestyle/E70.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
#: E701:1:5
|
||||
if a: a = False
|
||||
#: E701:1:40
|
||||
if not header or header[:6] != 'bytes=': return
|
||||
#: E702:1:10
|
||||
a = False; b = True
|
||||
#: E702:1:17
|
||||
import bdist_egg; bdist_egg.write_safety_flag(cmd.egg_info, safe)
|
||||
#: E703:1:13
|
||||
import shlex;
|
||||
#: E702:1:9 E703:1:23
|
||||
del a[:]; a.append(42);
|
||||
#: E704:1:1
|
||||
def f(x): return 2
|
||||
#: E704:1:1
|
||||
async def f(x): return 2
|
||||
#: E704:1:1 E271:1:6
|
||||
async def f(x): return 2
|
||||
#: E704:1:1 E226:1:19
|
||||
def f(x): return 2*x
|
||||
#: E704:2:5 E226:2:23
|
||||
while all is round:
|
||||
def f(x): return 2*x
|
||||
#: E704:1:8 E702:1:11 E703:1:14
|
||||
if True: x; y;
|
||||
#: E701:1:8
|
||||
if True: lambda a: b
|
||||
#: E701:1:10
|
||||
if a := 1: pass
|
||||
# E701:1:4 E701:2:18 E701:3:8
|
||||
try: lambda foo: bar
|
||||
except ValueError: pass
|
||||
finally: pass
|
||||
# E701:1:7
|
||||
class C: pass
|
||||
# E701:1:7
|
||||
with C(): pass
|
||||
# E701:1:14
|
||||
async with C(): pass
|
||||
#:
|
||||
lambda a: b
|
||||
#:
|
||||
a: List[str] = []
|
||||
#:
|
||||
if a := 1:
|
||||
pass
|
||||
4
crates/ruff/resources/test/fixtures/pyflakes/F401_9.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/pyflakes/F401_9.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Test: late-binding of `__all__`."""
|
||||
|
||||
__all__ = ("bar",)
|
||||
from foo import bar, baz
|
||||
7
crates/ruff/resources/test/fixtures/pyflakes/F822_2.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/pyflakes/F822_2.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
__all__ = ["foo"]
|
||||
|
||||
foo = 1
|
||||
|
||||
|
||||
def bar():
|
||||
pass
|
||||
57
crates/ruff/resources/test/fixtures/pylint/bad_string_format_type.py
vendored
Normal file
57
crates/ruff/resources/test/fixtures/pylint/bad_string_format_type.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# Errors
|
||||
print("foo %(foo)d bar %(bar)d" % {"foo": "1", "bar": "2"})
|
||||
|
||||
"foo %e bar %s" % ("1", 2)
|
||||
|
||||
"%d" % "1"
|
||||
"%o" % "1"
|
||||
"%(key)d" % {"key": "1"}
|
||||
"%x" % 1.1
|
||||
"%(key)x" % {"key": 1.1}
|
||||
"%d" % []
|
||||
"%d" % ([],)
|
||||
"%(key)d" % {"key": []}
|
||||
print("%d" % ("%s" % ("nested",),))
|
||||
"%d" % ((1, 2, 3),)
|
||||
|
||||
# False negatives
|
||||
WORD = "abc"
|
||||
"%d" % WORD
|
||||
"%d %s" % (WORD, WORD)
|
||||
VALUES_TO_FORMAT = (1, "2", 3.0)
|
||||
"%d %d %f" % VALUES_TO_FORMAT
|
||||
|
||||
# OK
|
||||
"%d %s %f" % VALUES_TO_FORMAT
|
||||
"%s" % "1"
|
||||
"%s %s %s" % ("1", 2, 3.5)
|
||||
print("%d %d"
|
||||
%
|
||||
(1, 1.1))
|
||||
"%s" % 1
|
||||
"%d" % 1
|
||||
"%f" % 1
|
||||
"%s" % 1
|
||||
"%(key)s" % {"key": 1}
|
||||
"%d" % 1
|
||||
"%(key)d" % {"key": 1}
|
||||
"%f" % 1
|
||||
"%(key)f" % {"key": 1}
|
||||
"%d" % 1.1
|
||||
"%(key)d" % {"key": 1.1}
|
||||
"%s" % []
|
||||
"%(key)s" % {"key": []}
|
||||
"%s" % None
|
||||
"%(key)s" % {"key": None}
|
||||
print("%s" % ("%s" % ("nested",),))
|
||||
print("%s" % ("%d" % (5,),))
|
||||
"%d %d" % "1"
|
||||
"%d" "%d" % "1"
|
||||
"-%f" % time.time()
|
||||
"%r" % (object['dn'],)
|
||||
r'\%03o' % (ord(c),)
|
||||
('%02X' % int(_) for _ in o)
|
||||
"%s;range=%d-*" % (attr, upper + 1)
|
||||
"%d" % (len(foo),)
|
||||
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
|
||||
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
|
||||
@@ -1,6 +1,3 @@
|
||||
# E2502
|
||||
print("\u202B\u202E\u05e9\u05DC\u05D5\u05DD\u202C")
|
||||
|
||||
# E2502
|
||||
print("שלום")
|
||||
|
||||
@@ -20,5 +17,12 @@ def subtract_funds(account: str, amount: int):
|
||||
return
|
||||
|
||||
|
||||
# OK
|
||||
print("\u202B\u202E\u05e9\u05DC\u05D5\u05DD\u202C")
|
||||
|
||||
|
||||
# OK
|
||||
print("\N{RIGHT-TO-LEFT MARK}")
|
||||
|
||||
# OK
|
||||
print("Hello World")
|
||||
|
||||
17
crates/ruff/resources/test/fixtures/pylint/yield_in_init.py
vendored
Normal file
17
crates/ruff/resources/test/fixtures/pylint/yield_in_init.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
def a():
|
||||
yield
|
||||
|
||||
def __init__():
|
||||
yield
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
yield
|
||||
|
||||
|
||||
class B:
|
||||
def __init__(self):
|
||||
yield from self.gen()
|
||||
|
||||
def gen(self):
|
||||
yield 5
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use crate::ast::helpers::to_call_path;
|
||||
use crate::ast::helpers::{map_callable, to_call_path};
|
||||
use crate::ast::types::{Scope, ScopeKind};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -29,18 +29,20 @@ pub fn classify(
|
||||
if decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a static method decorator (like
|
||||
// `@staticmethod`).
|
||||
checker.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
staticmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| call_path == to_call_path(decorator))
|
||||
})
|
||||
checker
|
||||
.resolve_call_path(map_callable(expr))
|
||||
.map_or(false, |call_path| {
|
||||
staticmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| call_path == to_call_path(decorator))
|
||||
})
|
||||
}) {
|
||||
FunctionType::StaticMethod
|
||||
} else if CLASS_METHODS.contains(&name)
|
||||
// Special-case class method, like `__new__`.
|
||||
|| scope.bases.iter().any(|expr| {
|
||||
// The class itself extends a known metaclass, so all methods are class methods.
|
||||
checker.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
checker.resolve_call_path(map_callable(expr)).map_or(false, |call_path| {
|
||||
METACLASS_BASES
|
||||
.iter()
|
||||
.any(|(module, member)| call_path.as_slice() == [*module, *member])
|
||||
@@ -48,7 +50,7 @@ pub fn classify(
|
||||
})
|
||||
|| decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a class method decorator (like `@classmethod`).
|
||||
checker.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
checker.resolve_call_path(map_callable(expr)).map_or(false, |call_path| {
|
||||
classmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| call_path == to_call_path(decorator))
|
||||
|
||||
@@ -568,6 +568,17 @@ pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
|
||||
arg_names
|
||||
}
|
||||
|
||||
/// Given an [`Expr`] that can be callable or not (like a decorator, which could
|
||||
/// be used with or without explicit call syntax), return the underlying
|
||||
/// callable.
|
||||
pub fn map_callable(decorator: &Expr) -> &Expr {
|
||||
if let ExprKind::Call { func, .. } = &decorator.node {
|
||||
func
|
||||
} else {
|
||||
decorator
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a statement or expression includes at least one comment.
|
||||
pub fn has_comments<T>(located: &Located<T>, locator: &Locator) -> bool {
|
||||
let start = if match_leading_content(located, locator) {
|
||||
|
||||
@@ -6,8 +6,6 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use nohash_hasher::IntMap;
|
||||
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use ruff_python::typing::TYPING_EXTENSIONS;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_common::cformat::{CFormatError, CFormatErrorType};
|
||||
use rustpython_parser::ast::{
|
||||
@@ -17,6 +15,9 @@ use rustpython_parser::ast::{
|
||||
use rustpython_parser::parser;
|
||||
use smallvec::smallvec;
|
||||
|
||||
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use ruff_python::typing::TYPING_EXTENSIONS;
|
||||
|
||||
use crate::ast::helpers::{
|
||||
binding_range, collect_call_path, extract_handler_names, from_relative_import, to_module_path,
|
||||
};
|
||||
@@ -30,16 +31,15 @@ use crate::ast::typing::{match_annotated_subscript, Callable, SubscriptKind};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{branch_detection, cast, helpers, operations, typing, visitor};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable};
|
||||
use crate::noqa::Directive;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::{
|
||||
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
|
||||
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
||||
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format,
|
||||
flake8_pie, flake8_print, flake8_pytest_style, flake8_raise, flake8_return, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
|
||||
flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes,
|
||||
pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return,
|
||||
flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
|
||||
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -282,7 +282,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
/// Return `true` if a `Rule` is disabled by a `noqa` directive.
|
||||
pub fn is_ignored(&self, code: &Rule, lineno: usize) -> bool {
|
||||
pub fn rule_is_ignored(&self, code: &Rule, lineno: usize) -> bool {
|
||||
// TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`.
|
||||
// However, in rare cases, we need to check them here. For example, when
|
||||
// removing unused imports, we create a single fix that's applied to all
|
||||
@@ -293,16 +293,7 @@ impl<'a> Checker<'a> {
|
||||
if matches!(self.noqa, flags::Noqa::Disabled) {
|
||||
return false;
|
||||
}
|
||||
let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
let line = self.locator.slice_source_code_range(&Range::new(
|
||||
Location::new(*noqa_lineno, 0),
|
||||
Location::new(noqa_lineno + 1, 0),
|
||||
));
|
||||
match noqa::extract_noqa_directive(line) {
|
||||
Directive::None => false,
|
||||
Directive::All(..) => true,
|
||||
Directive::Codes(.., codes) => noqa::includes(code, &codes),
|
||||
}
|
||||
noqa::rule_is_ignored(code, lineno, self.noqa_line_for, self.locator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1235,9 +1226,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::ImportStarUsed) {
|
||||
if self.settings.rules.enabled(&Rule::ImportStar) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportStarUsed {
|
||||
pyflakes::rules::ImportStar {
|
||||
name: helpers::format_import_from(
|
||||
level.as_ref(),
|
||||
module.as_deref(),
|
||||
@@ -1300,9 +1291,12 @@ where
|
||||
if self.settings.rules.enabled(&Rule::RelativeImports) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_tidy_imports::relative_imports::banned_relative_import(
|
||||
self,
|
||||
stmt,
|
||||
level.as_ref(),
|
||||
module.as_deref(),
|
||||
&self.settings.flake8_tidy_imports.ban_relative_imports,
|
||||
self.path,
|
||||
)
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
@@ -1537,7 +1531,7 @@ where
|
||||
if self.settings.rules.enabled(&Rule::AssertTuple) {
|
||||
pyflakes::rules::assert_tuple(self, stmt, test);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::DoNotAssertFalse) {
|
||||
if self.settings.rules.enabled(&Rule::AssertFalse) {
|
||||
flake8_bugbear::rules::assert_false(
|
||||
self,
|
||||
stmt,
|
||||
@@ -1545,7 +1539,7 @@ where
|
||||
msg.as_ref().map(|expr| &**expr),
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::AssertUsed) {
|
||||
if self.settings.rules.enabled(&Rule::Assert) {
|
||||
self.diagnostics
|
||||
.push(flake8_bandit::rules::assert_used(stmt));
|
||||
}
|
||||
@@ -1704,9 +1698,9 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.rules.enabled(&Rule::DoNotAssignLambda) {
|
||||
if self.settings.rules.enabled(&Rule::LambdaAssignment) {
|
||||
if let [target] = &targets[..] {
|
||||
pycodestyle::rules::do_not_assign_lambda(self, target, value, stmt);
|
||||
pycodestyle::rules::lambda_assignment(self, target, value, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1722,6 +1716,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::PrefixTypeParams) {
|
||||
if self.path.extension().map_or(false, |ext| ext == "pyi") {
|
||||
flake8_pyi::rules::prefix_type_params(self, value, targets);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::UselessMetaclassType) {
|
||||
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
@@ -1754,9 +1754,9 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if self.settings.rules.enabled(&Rule::DoNotAssignLambda) {
|
||||
if self.settings.rules.enabled(&Rule::LambdaAssignment) {
|
||||
if let Some(value) = value {
|
||||
pycodestyle::rules::do_not_assign_lambda(self, target, value, stmt);
|
||||
pycodestyle::rules::lambda_assignment(self, target, value, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2372,7 +2372,7 @@ where
|
||||
}
|
||||
|
||||
// flake8-bandit
|
||||
if self.settings.rules.enabled(&Rule::ExecUsed) {
|
||||
if self.settings.rules.enabled(&Rule::ExecBuiltin) {
|
||||
if let Some(diagnostic) = flake8_bandit::rules::exec_used(expr, func) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -2405,6 +2405,9 @@ where
|
||||
self.diagnostics
|
||||
.extend(flake8_bandit::rules::hardcoded_password_func_arg(keywords));
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
@@ -2790,11 +2793,17 @@ where
|
||||
if self.settings.rules.enabled(&Rule::YieldOutsideFunction) {
|
||||
pyflakes::rules::yield_outside_function(self, expr);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::YieldInInit) {
|
||||
pylint::rules::yield_in_init(self, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::YieldFrom { .. } => {
|
||||
if self.settings.rules.enabled(&Rule::YieldOutsideFunction) {
|
||||
pyflakes::rules::yield_outside_function(self, expr);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::YieldInInit) {
|
||||
pylint::rules::yield_in_init(self, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::Await { .. } => {
|
||||
if self.settings.rules.enabled(&Rule::YieldOutsideFunction) {
|
||||
@@ -2812,6 +2821,9 @@ where
|
||||
{
|
||||
pyflakes::rules::f_string_missing_placeholders(expr, values, self);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
left,
|
||||
@@ -2973,6 +2985,12 @@ where
|
||||
if self.settings.rules.enabled(&Rule::PrintfStringFormatting) {
|
||||
pyupgrade::rules::printf_string_formatting(self, expr, left, right);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::BadStringFormatType) {
|
||||
pylint::rules::bad_string_format_type(self, expr, right);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
@@ -2994,6 +3012,9 @@ where
|
||||
{
|
||||
ruff::rules::unpack_instead_of_concatenating_to_collection_literal(self, expr);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
let check_not_in = self.settings.rules.enabled(&Rule::NotInTest);
|
||||
@@ -3137,9 +3158,6 @@ where
|
||||
if self.settings.rules.enabled(&Rule::RewriteUnicodeLiteral) {
|
||||
pyupgrade::rules::rewrite_unicode_literal(self, expr, kind.as_deref());
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::BidirectionalUnicode) {
|
||||
pylint::rules::bidirectional_unicode(self, expr, value);
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, body, .. } => {
|
||||
if self.settings.rules.enabled(&Rule::PreferListBuiltin) {
|
||||
@@ -3521,8 +3539,8 @@ where
|
||||
ExcepthandlerKind::ExceptHandler {
|
||||
type_, name, body, ..
|
||||
} => {
|
||||
if self.settings.rules.enabled(&Rule::DoNotUseBareExcept) {
|
||||
if let Some(diagnostic) = pycodestyle::rules::do_not_use_bare_except(
|
||||
if self.settings.rules.enabled(&Rule::BareExcept) {
|
||||
if let Some(diagnostic) = pycodestyle::rules::bare_except(
|
||||
type_.as_deref(),
|
||||
body,
|
||||
excepthandler,
|
||||
@@ -3549,6 +3567,17 @@ where
|
||||
if self.settings.rules.enabled(&Rule::TryExceptPass) {
|
||||
flake8_bandit::rules::try_except_pass(
|
||||
self,
|
||||
excepthandler,
|
||||
type_.as_deref(),
|
||||
name.as_deref(),
|
||||
body,
|
||||
self.settings.flake8_bandit.check_typed_exception,
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::TryExceptContinue) {
|
||||
flake8_bandit::rules::try_except_continue(
|
||||
self,
|
||||
excepthandler,
|
||||
type_.as_deref(),
|
||||
name.as_deref(),
|
||||
body,
|
||||
@@ -3855,6 +3884,14 @@ impl<'a> Checker<'a> {
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found"))]
|
||||
}
|
||||
|
||||
pub fn current_scope_parent(&self) -> Option<&Scope> {
|
||||
self.scope_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.nth(1)
|
||||
.map(|index| &self.scopes[*index])
|
||||
}
|
||||
|
||||
pub fn current_scopes(&self) -> impl Iterator<Item = &Scope> {
|
||||
self.scope_stack
|
||||
.iter()
|
||||
@@ -4304,16 +4341,18 @@ impl<'a> Checker<'a> {
|
||||
} {
|
||||
let (all_names, all_names_flags) = extract_all_names(self, parent, current);
|
||||
|
||||
if self.settings.rules.enabled(&Rule::InvalidAllFormat)
|
||||
&& matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT)
|
||||
{
|
||||
pylint::rules::invalid_all_format(self, expr);
|
||||
if self.settings.rules.enabled(&Rule::InvalidAllFormat) {
|
||||
if matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT) {
|
||||
self.diagnostics
|
||||
.push(pylint::rules::invalid_all_format(expr));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::InvalidAllObject)
|
||||
&& matches!(all_names_flags, AllNamesFlags::INVALID_OBJECT)
|
||||
{
|
||||
pylint::rules::invalid_all_object(self, expr);
|
||||
if self.settings.rules.enabled(&Rule::InvalidAllObject) {
|
||||
if matches!(all_names_flags, AllNamesFlags::INVALID_OBJECT) {
|
||||
self.diagnostics
|
||||
.push(pylint::rules::invalid_all_object(expr));
|
||||
}
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
@@ -4578,11 +4617,54 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
let global_scope = &self.scopes[GLOBAL_SCOPE_INDEX];
|
||||
let all_names: Option<(&Vec<String>, Range)> = global_scope
|
||||
.bindings
|
||||
.get("__all__")
|
||||
.map(|index| &self.bindings[*index])
|
||||
.and_then(|binding| match &binding.kind {
|
||||
BindingKind::Export(names) => Some((names, binding.range)),
|
||||
_ => None,
|
||||
});
|
||||
let all_bindings: Option<(Vec<usize>, Range)> = all_names.map(|(names, range)| {
|
||||
(
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| global_scope.bindings.get(name.as_str()).copied())
|
||||
.collect(),
|
||||
range,
|
||||
)
|
||||
});
|
||||
if let Some((bindings, range)) = all_bindings {
|
||||
for index in bindings {
|
||||
self.bindings[index].mark_used(
|
||||
GLOBAL_SCOPE_INDEX,
|
||||
range,
|
||||
ExecutionContext::Runtime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract `__all__` names from the global scope.
|
||||
let all_names: Option<(Vec<&str>, Range)> = global_scope
|
||||
.bindings
|
||||
.get("__all__")
|
||||
.map(|index| &self.bindings[*index])
|
||||
.and_then(|binding| match &binding.kind {
|
||||
BindingKind::Export(names) => {
|
||||
Some((names.iter().map(String::as_str).collect(), binding.range))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||
// used at runtime, then by default, we avoid flagging any other
|
||||
// imports from that model as typing-only.
|
||||
let runtime_imports: Vec<Vec<&Binding>> = if !self.settings.flake8_type_checking.strict
|
||||
&& (self
|
||||
let runtime_imports: Vec<Vec<&Binding>> = if self.settings.flake8_type_checking.strict {
|
||||
vec![]
|
||||
} else {
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
|
||||
@@ -4597,29 +4679,41 @@ impl<'a> Checker<'a> {
|
||||
|| self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::TypingOnlyStandardLibraryImport))
|
||||
{
|
||||
self.scopes
|
||||
.iter()
|
||||
.map(|scope| {
|
||||
scope
|
||||
.bindings
|
||||
.values()
|
||||
.map(|index| &self.bindings[*index])
|
||||
.filter(|binding| {
|
||||
flake8_type_checking::helpers::is_valid_runtime_import(binding)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
.enabled(&Rule::TypingOnlyStandardLibraryImport)
|
||||
{
|
||||
self.scopes
|
||||
.iter()
|
||||
.map(|scope| {
|
||||
scope
|
||||
.bindings
|
||||
.values()
|
||||
.map(|index| &self.bindings[*index])
|
||||
.filter(|binding| {
|
||||
flake8_type_checking::helpers::is_valid_runtime_import(binding)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
for (index, stack) in self.dead_scopes.iter().rev() {
|
||||
let scope = &self.scopes[*index];
|
||||
|
||||
// F822
|
||||
if *index == GLOBAL_SCOPE_INDEX {
|
||||
if self.settings.rules.enabled(&Rule::UndefinedExport) {
|
||||
if let Some((names, range)) = &all_names {
|
||||
diagnostics.extend(pyflakes::rules::undefined_export(
|
||||
names, range, self.path, scope,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PLW0602
|
||||
if self
|
||||
.settings
|
||||
@@ -4648,35 +4742,6 @@ impl<'a> Checker<'a> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let all_binding: Option<&Binding> = scope
|
||||
.bindings
|
||||
.get("__all__")
|
||||
.map(|index| &self.bindings[*index]);
|
||||
let all_names: Option<Vec<&str>> =
|
||||
all_binding.and_then(|binding| match &binding.kind {
|
||||
BindingKind::Export(names) => Some(names.iter().map(String::as_str).collect()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if self.settings.rules.enabled(&Rule::UndefinedExport) {
|
||||
if !scope.import_starred && !self.path.ends_with("__init__.py") {
|
||||
if let Some(all_binding) = all_binding {
|
||||
if let Some(names) = &all_names {
|
||||
for &name in names {
|
||||
if !scope.bindings.contains_key(name) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: name.to_string(),
|
||||
},
|
||||
all_binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any bindings that were redefined in another scope, and remain
|
||||
// unused. Note that we only store references in `redefinitions` if
|
||||
// the bindings are in different scopes.
|
||||
@@ -4692,13 +4757,7 @@ impl<'a> Checker<'a> {
|
||||
| BindingKind::StarImportation(..)
|
||||
| BindingKind::FutureImportation
|
||||
) {
|
||||
// Skip used exports from `__all__`
|
||||
if binding.used()
|
||||
|| all_names
|
||||
.as_ref()
|
||||
.map(|names| names.contains(name))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if binding.used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4728,31 +4787,27 @@ impl<'a> Checker<'a> {
|
||||
|
||||
if self.settings.rules.enabled(&Rule::ImportStarUsage) {
|
||||
if scope.import_starred {
|
||||
if let Some(all_binding) = all_binding {
|
||||
if let Some(names) = &all_names {
|
||||
let mut from_list = vec![];
|
||||
for binding in
|
||||
scope.bindings.values().map(|index| &self.bindings[*index])
|
||||
{
|
||||
if let BindingKind::StarImportation(level, module) = &binding.kind {
|
||||
from_list.push(helpers::format_import_from(
|
||||
level.as_ref(),
|
||||
module.as_deref(),
|
||||
));
|
||||
}
|
||||
if let Some((names, range)) = &all_names {
|
||||
let mut from_list = vec![];
|
||||
for binding in scope.bindings.values().map(|index| &self.bindings[*index]) {
|
||||
if let BindingKind::StarImportation(level, module) = &binding.kind {
|
||||
from_list.push(helpers::format_import_from(
|
||||
level.as_ref(),
|
||||
module.as_deref(),
|
||||
));
|
||||
}
|
||||
from_list.sort();
|
||||
}
|
||||
from_list.sort();
|
||||
|
||||
for &name in names {
|
||||
if !scope.bindings.contains_key(name) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportStarUsage {
|
||||
name: name.to_string(),
|
||||
sources: from_list.clone(),
|
||||
},
|
||||
all_binding.range,
|
||||
));
|
||||
}
|
||||
for &name in names {
|
||||
if !scope.bindings.contains_key(name) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportStarUsage {
|
||||
name: name.to_string(),
|
||||
sources: from_list.clone(),
|
||||
},
|
||||
*range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4820,7 +4875,7 @@ impl<'a> Checker<'a> {
|
||||
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
|
||||
FxHashMap::default();
|
||||
|
||||
for (name, index) in &scope.bindings {
|
||||
for index in scope.bindings.values() {
|
||||
let binding = &self.bindings[*index];
|
||||
|
||||
let full_name = match &binding.kind {
|
||||
@@ -4830,13 +4885,7 @@ impl<'a> Checker<'a> {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Skip used exports from `__all__`
|
||||
if binding.used()
|
||||
|| all_names
|
||||
.as_ref()
|
||||
.map(|names| names.contains(name))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if binding.used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4853,9 +4902,9 @@ impl<'a> Checker<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
if self.is_ignored(&Rule::UnusedImport, diagnostic_lineno)
|
||||
if self.rule_is_ignored(&Rule::UnusedImport, diagnostic_lineno)
|
||||
|| parent_lineno.map_or(false, |parent_lineno| {
|
||||
self.is_ignored(&Rule::UnusedImport, parent_lineno)
|
||||
self.rule_is_ignored(&Rule::UnusedImport, parent_lineno)
|
||||
})
|
||||
{
|
||||
ignored
|
||||
@@ -4987,10 +5036,7 @@ impl<'a> Checker<'a> {
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::MissingReturnTypeClassMethod)
|
||||
|| self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression);
|
||||
|| self.settings.rules.enabled(&Rule::AnyType);
|
||||
let enforce_docstrings = self.settings.rules.enabled(&Rule::PublicModule)
|
||||
|| self.settings.rules.enabled(&Rule::PublicClass)
|
||||
|| self.settings.rules.enabled(&Rule::PublicMethod)
|
||||
@@ -5030,11 +5076,11 @@ impl<'a> Checker<'a> {
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::SectionUnderlineNotOverIndented)
|
||||
|| self.settings.rules.enabled(&Rule::UsesTripleQuotes)
|
||||
|| self.settings.rules.enabled(&Rule::TripleSingleQuotes)
|
||||
|| self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UsesRPrefixForBackslashedContent)
|
||||
.enabled(&Rule::EscapeSequenceInDocstring)
|
||||
|| self.settings.rules.enabled(&Rule::EndsInPeriod)
|
||||
|| self.settings.rules.enabled(&Rule::NonImperativeMood)
|
||||
|| self.settings.rules.enabled(&Rule::NoSignature)
|
||||
@@ -5178,13 +5224,13 @@ impl<'a> Checker<'a> {
|
||||
{
|
||||
pydocstyle::rules::multi_line_summary_start(self, &docstring);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UsesTripleQuotes) {
|
||||
if self.settings.rules.enabled(&Rule::TripleSingleQuotes) {
|
||||
pydocstyle::rules::triple_quotes(self, &docstring);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UsesRPrefixForBackslashedContent)
|
||||
.enabled(&Rule::EscapeSequenceInDocstring)
|
||||
{
|
||||
pydocstyle::rules::backslashes(self, &docstring);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::rules::pycodestyle::rules::{
|
||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||
};
|
||||
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
|
||||
use crate::rules::pylint;
|
||||
use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code::Stylist;
|
||||
@@ -41,6 +42,7 @@ pub fn check_physical_lines(
|
||||
.rules
|
||||
.enabled(&Rule::PEP3120UnnecessaryCodingComment);
|
||||
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
|
||||
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
|
||||
|
||||
let fix_unnecessary_coding_comment = matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings
|
||||
@@ -137,6 +139,10 @@ pub fn check_physical_lines(
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_bidirectional_unicode {
|
||||
diagnostics.extend(pylint::rules::bidirectional_unicode(index, line));
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_no_newline_at_end_of_file {
|
||||
|
||||
@@ -32,8 +32,18 @@ pub fn check_tokens(
|
||||
let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString)
|
||||
|| settings.rules.enabled(&Rule::BadQuotesMultilineString)
|
||||
|| settings.rules.enabled(&Rule::BadQuotesDocstring)
|
||||
|| settings.rules.enabled(&Rule::AvoidQuoteEscape);
|
||||
|| settings.rules.enabled(&Rule::AvoidableEscapedQuote);
|
||||
let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode);
|
||||
let enforce_compound_statements = settings
|
||||
.rules
|
||||
.enabled(&Rule::MultipleStatementsOnOneLineColon)
|
||||
|| settings
|
||||
.rules
|
||||
.enabled(&Rule::MultipleStatementsOnOneLineSemicolon)
|
||||
|| settings.rules.enabled(&Rule::UselessSemicolon)
|
||||
|| settings
|
||||
.rules
|
||||
.enabled(&Rule::MultipleStatementsOnOneLineDef);
|
||||
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
|
||||
let enforce_implicit_string_concatenation = settings
|
||||
.rules
|
||||
@@ -48,10 +58,8 @@ pub fn check_tokens(
|
||||
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
|
||||
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
|
||||
|
||||
if enforce_ambiguous_unicode_character
|
||||
|| enforce_commented_out_code
|
||||
|| enforce_invalid_escape_sequence
|
||||
{
|
||||
// RUF001, RUF002, RUF003
|
||||
if enforce_ambiguous_unicode_character {
|
||||
let mut state_machine = StateMachine::default();
|
||||
for &(start, ref tok, end) in tokens.iter().flatten() {
|
||||
let is_docstring = if enforce_ambiguous_unicode_character {
|
||||
@@ -60,54 +68,64 @@ pub fn check_tokens(
|
||||
false
|
||||
};
|
||||
|
||||
// RUF001, RUF002, RUF003
|
||||
if enforce_ambiguous_unicode_character {
|
||||
if matches!(tok, Tok::String { .. } | Tok::Comment(_)) {
|
||||
diagnostics.extend(ruff::rules::ambiguous_unicode_character(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
if is_docstring {
|
||||
Context::Docstring
|
||||
} else {
|
||||
Context::String
|
||||
}
|
||||
if matches!(tok, Tok::String { .. } | Tok::Comment(_)) {
|
||||
diagnostics.extend(ruff::rules::ambiguous_unicode_character(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
if is_docstring {
|
||||
Context::Docstring
|
||||
} else {
|
||||
Context::Comment
|
||||
},
|
||||
settings,
|
||||
autofix,
|
||||
));
|
||||
}
|
||||
Context::String
|
||||
}
|
||||
} else {
|
||||
Context::Comment
|
||||
},
|
||||
settings,
|
||||
autofix,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eradicate
|
||||
if enforce_commented_out_code {
|
||||
if matches!(tok, Tok::Comment(_)) {
|
||||
if let Some(diagnostic) =
|
||||
eradicate::rules::commented_out_code(locator, start, end, settings, autofix)
|
||||
{
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
diagnostics.extend(pycodestyle::rules::invalid_escape_sequence(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
|
||||
));
|
||||
// ERA001
|
||||
if enforce_commented_out_code {
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::Comment(_)) {
|
||||
if let Some(diagnostic) =
|
||||
eradicate::rules::commented_out_code(locator, *start, *end, settings, autofix)
|
||||
{
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
diagnostics.extend(pycodestyle::rules::invalid_escape_sequence(
|
||||
locator,
|
||||
*start,
|
||||
*end,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// E701, E702, E703, E704
|
||||
if enforce_compound_statements {
|
||||
diagnostics.extend(
|
||||
pycodestyle::rules::compound_statements(tokens)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
}
|
||||
|
||||
// Q001, Q002, Q003
|
||||
if enforce_quotes {
|
||||
diagnostics.extend(
|
||||
|
||||
@@ -282,6 +282,10 @@ mod tests {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: RuleCodePrefix::F841.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "*.pyi".to_string(),
|
||||
prefix: RuleCodePrefix::E704.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Default)]
|
||||
enum State {
|
||||
// Start of the module: first string gets marked as a docstring.
|
||||
#[default]
|
||||
ExpectModuleDocstring,
|
||||
// After seeing a class definition, we're waiting for the block colon (and do bracket
|
||||
// counting).
|
||||
@@ -23,25 +24,13 @@ enum State {
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StateMachine {
|
||||
state: State,
|
||||
bracket_count: usize,
|
||||
}
|
||||
|
||||
impl Default for StateMachine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
state: State::ExpectModuleDocstring,
|
||||
bracket_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(&mut self, tok: &Tok) -> bool {
|
||||
if matches!(
|
||||
tok,
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
//!
|
||||
//! [Ruff]: https://github.com/charliermarsh/ruff
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
pub use violation::{AutofixKind, Availability as AutofixAvailability};
|
||||
|
||||
mod assert_yaml_snapshot;
|
||||
mod ast;
|
||||
mod autofix;
|
||||
@@ -34,20 +39,12 @@ mod vendor;
|
||||
mod violation;
|
||||
mod visibility;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
pub use violation::{AutofixKind, Availability as AutofixAvailability};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(target_family = "wasm"))] {
|
||||
pub mod packaging;
|
||||
|
||||
mod lib_native;
|
||||
pub use lib_native::check;
|
||||
} else {
|
||||
if #[cfg(target_family = "wasm")] {
|
||||
mod lib_wasm;
|
||||
pub use lib_wasm::check;
|
||||
} else {
|
||||
pub mod packaging;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use path_absolutize::path_dedot;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::linter::check_path;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::resolver::Relativity;
|
||||
use crate::rustpython_helpers::tokenize;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::{flags, pyproject, Settings};
|
||||
use crate::source_code::{Indexer, Locator, Stylist};
|
||||
use crate::{directives, packaging, resolver};
|
||||
|
||||
/// Load the relevant `Settings` for a given `Path`.
|
||||
fn resolve(path: &Path) -> Result<Settings> {
|
||||
if let Some(pyproject) = pyproject::find_settings_toml(path)? {
|
||||
// First priority: `pyproject.toml` in the current `Path`.
|
||||
Ok(resolver::resolve_settings(&pyproject, &Relativity::Parent)?.lib)
|
||||
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||
// Second priority: user-specific `pyproject.toml`.
|
||||
Ok(resolver::resolve_settings(&pyproject, &Relativity::Cwd)?.lib)
|
||||
} else {
|
||||
// Fallback: default settings.
|
||||
Settings::from_configuration(Configuration::default(), &path_dedot::CWD)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Diagnostic>> {
|
||||
// Load the relevant `Settings` for the given `Path`.
|
||||
let settings = resolve(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_contents(contents, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer: Indexer = tokens.as_slice().into();
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives =
|
||||
directives::extract_directives(&tokens, directives::Flags::from_settings(&settings));
|
||||
|
||||
// Generate diagnostics.
|
||||
let result = check_path(
|
||||
path,
|
||||
packaging::detect_package_root(path, &settings.namespace_packages),
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
&directives,
|
||||
&settings,
|
||||
autofix.into(),
|
||||
flags::Noqa::Enabled,
|
||||
);
|
||||
|
||||
Ok(result.data)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use crate::checkers::tokens::check_tokens;
|
||||
use crate::directives::Directives;
|
||||
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||
use crate::message::{Message, Source};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::noqa::{add_noqa, rule_is_ignored};
|
||||
use crate::registry::{Diagnostic, LintSource, Rule};
|
||||
use crate::rules::pycodestyle;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -149,7 +149,17 @@ pub fn check_path(
|
||||
if settings.rules.enabled(&Rule::SyntaxError) {
|
||||
pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error);
|
||||
}
|
||||
error = Some(parse_error);
|
||||
|
||||
// If the syntax error is ignored, suppress it (regardless of whether
|
||||
// `Rule::SyntaxError` is enabled).
|
||||
if !rule_is_ignored(
|
||||
&Rule::SyntaxError,
|
||||
parse_error.location.row(),
|
||||
&directives.noqa_line_for,
|
||||
locator,
|
||||
) {
|
||||
error = Some(parse_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ use nohash_hasher::IntMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::source_code::LineEnding;
|
||||
use crate::source_code::{LineEnding, Locator};
|
||||
|
||||
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
@@ -76,6 +78,25 @@ pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
|
||||
.any(|candidate| needle == get_redirect_target(candidate).unwrap_or(candidate))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`.
|
||||
pub fn rule_is_ignored(
|
||||
code: &Rule,
|
||||
lineno: usize,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
locator: &Locator,
|
||||
) -> bool {
|
||||
let noqa_lineno = noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
let line = locator.slice_source_code_range(&Range::new(
|
||||
Location::new(*noqa_lineno, 0),
|
||||
Location::new(noqa_lineno + 1, 0),
|
||||
));
|
||||
match extract_noqa_directive(line) {
|
||||
Directive::None => false,
|
||||
Directive::All(..) => true,
|
||||
Directive::Codes(.., codes) => includes(code, &codes),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
|
||||
@@ -60,13 +60,17 @@ ruff_macros::define_rule_mapping!(
|
||||
E401 => rules::pycodestyle::rules::MultipleImportsOnOneLine,
|
||||
E402 => rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
|
||||
E501 => rules::pycodestyle::rules::LineTooLong,
|
||||
E701 => rules::pycodestyle::rules::MultipleStatementsOnOneLineColon,
|
||||
E702 => rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon,
|
||||
E703 => rules::pycodestyle::rules::UselessSemicolon,
|
||||
E704 => rules::pycodestyle::rules::MultipleStatementsOnOneLineDef,
|
||||
E711 => rules::pycodestyle::rules::NoneComparison,
|
||||
E712 => rules::pycodestyle::rules::TrueFalseComparison,
|
||||
E713 => rules::pycodestyle::rules::NotInTest,
|
||||
E714 => rules::pycodestyle::rules::NotIsTest,
|
||||
E721 => rules::pycodestyle::rules::TypeComparison,
|
||||
E722 => rules::pycodestyle::rules::DoNotUseBareExcept,
|
||||
E731 => rules::pycodestyle::rules::DoNotAssignLambda,
|
||||
E722 => rules::pycodestyle::rules::BareExcept,
|
||||
E731 => rules::pycodestyle::rules::LambdaAssignment,
|
||||
E741 => rules::pycodestyle::rules::AmbiguousVariableName,
|
||||
E742 => rules::pycodestyle::rules::AmbiguousClassName,
|
||||
E743 => rules::pycodestyle::rules::AmbiguousFunctionName,
|
||||
@@ -79,7 +83,7 @@ ruff_macros::define_rule_mapping!(
|
||||
// pyflakes
|
||||
F401 => rules::pyflakes::rules::UnusedImport,
|
||||
F402 => rules::pyflakes::rules::ImportShadowedByLoopVar,
|
||||
F403 => rules::pyflakes::rules::ImportStarUsed,
|
||||
F403 => rules::pyflakes::rules::ImportStar,
|
||||
F404 => rules::pyflakes::rules::LateFutureImport,
|
||||
F405 => rules::pyflakes::rules::ImportStarUsage,
|
||||
F406 => rules::pyflakes::rules::ImportStarNotPermitted,
|
||||
@@ -121,8 +125,10 @@ ruff_macros::define_rule_mapping!(
|
||||
F842 => rules::pyflakes::rules::UnusedAnnotation,
|
||||
F901 => rules::pyflakes::rules::RaiseNotImplemented,
|
||||
// pylint
|
||||
PLE0100 => rules::pylint::rules::YieldInInit,
|
||||
PLE0604 => rules::pylint::rules::InvalidAllObject,
|
||||
PLE0605 => rules::pylint::rules::InvalidAllFormat,
|
||||
PLE1307 => rules::pylint::rules::BadStringFormatType,
|
||||
PLE2502 => rules::pylint::rules::BidirectionalUnicode,
|
||||
PLE1310 => rules::pylint::rules::BadStrStripCall,
|
||||
PLC0414 => rules::pylint::rules::UselessImportAlias,
|
||||
@@ -156,7 +162,7 @@ ruff_macros::define_rule_mapping!(
|
||||
B008 => rules::flake8_bugbear::rules::FunctionCallArgumentDefault,
|
||||
B009 => rules::flake8_bugbear::rules::GetAttrWithConstant,
|
||||
B010 => rules::flake8_bugbear::rules::SetAttrWithConstant,
|
||||
B011 => rules::flake8_bugbear::rules::DoNotAssertFalse,
|
||||
B011 => rules::flake8_bugbear::rules::AssertFalse,
|
||||
B012 => rules::flake8_bugbear::rules::JumpStatementInFinally,
|
||||
B013 => rules::flake8_bugbear::rules::RedundantTupleInExceptionHandler,
|
||||
B014 => rules::flake8_bugbear::rules::DuplicateHandlerException,
|
||||
@@ -221,7 +227,7 @@ ruff_macros::define_rule_mapping!(
|
||||
Q000 => rules::flake8_quotes::rules::BadQuotesInlineString,
|
||||
Q001 => rules::flake8_quotes::rules::BadQuotesMultilineString,
|
||||
Q002 => rules::flake8_quotes::rules::BadQuotesDocstring,
|
||||
Q003 => rules::flake8_quotes::rules::AvoidQuoteEscape,
|
||||
Q003 => rules::flake8_quotes::rules::AvoidableEscapedQuote,
|
||||
// flake8-annotations
|
||||
ANN001 => rules::flake8_annotations::rules::MissingTypeFunctionArgument,
|
||||
ANN002 => rules::flake8_annotations::rules::MissingTypeArgs,
|
||||
@@ -233,7 +239,7 @@ ruff_macros::define_rule_mapping!(
|
||||
ANN204 => rules::flake8_annotations::rules::MissingReturnTypeSpecialMethod,
|
||||
ANN205 => rules::flake8_annotations::rules::MissingReturnTypeStaticMethod,
|
||||
ANN206 => rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
|
||||
ANN401 => rules::flake8_annotations::rules::DynamicallyTypedExpression,
|
||||
ANN401 => rules::flake8_annotations::rules::AnyType,
|
||||
// flake8-2020
|
||||
YTT101 => rules::flake8_2020::rules::SysVersionSlice3Referenced,
|
||||
YTT102 => rules::flake8_2020::rules::SysVersion2Referenced,
|
||||
@@ -332,8 +338,8 @@ ruff_macros::define_rule_mapping!(
|
||||
D213 => rules::pydocstyle::rules::MultiLineSummarySecondLine,
|
||||
D214 => rules::pydocstyle::rules::SectionNotOverIndented,
|
||||
D215 => rules::pydocstyle::rules::SectionUnderlineNotOverIndented,
|
||||
D300 => rules::pydocstyle::rules::UsesTripleQuotes,
|
||||
D301 => rules::pydocstyle::rules::UsesRPrefixForBackslashedContent,
|
||||
D300 => rules::pydocstyle::rules::TripleSingleQuotes,
|
||||
D301 => rules::pydocstyle::rules::EscapeSequenceInDocstring,
|
||||
D400 => rules::pydocstyle::rules::EndsInPeriod,
|
||||
D401 => rules::pydocstyle::rules::NonImperativeMood,
|
||||
D402 => rules::pydocstyle::rules::NoSignature,
|
||||
@@ -376,15 +382,17 @@ ruff_macros::define_rule_mapping!(
|
||||
// eradicate
|
||||
ERA001 => rules::eradicate::rules::CommentedOutCode,
|
||||
// flake8-bandit
|
||||
S101 => rules::flake8_bandit::rules::AssertUsed,
|
||||
S102 => rules::flake8_bandit::rules::ExecUsed,
|
||||
S101 => rules::flake8_bandit::rules::Assert,
|
||||
S102 => rules::flake8_bandit::rules::ExecBuiltin,
|
||||
S103 => rules::flake8_bandit::rules::BadFilePermissions,
|
||||
S104 => rules::flake8_bandit::rules::HardcodedBindAllInterfaces,
|
||||
S105 => rules::flake8_bandit::rules::HardcodedPasswordString,
|
||||
S106 => rules::flake8_bandit::rules::HardcodedPasswordFuncArg,
|
||||
S107 => rules::flake8_bandit::rules::HardcodedPasswordDefault,
|
||||
S608 => rules::flake8_bandit::rules::HardcodedSQLExpression,
|
||||
S108 => rules::flake8_bandit::rules::HardcodedTempFile,
|
||||
S110 => rules::flake8_bandit::rules::TryExceptPass,
|
||||
S112 => rules::flake8_bandit::rules::TryExceptContinue,
|
||||
S113 => rules::flake8_bandit::rules::RequestWithoutTimeout,
|
||||
S324 => rules::flake8_bandit::rules::HashlibInsecureHashFunction,
|
||||
S501 => rules::flake8_bandit::rules::RequestWithNoCertValidation,
|
||||
@@ -437,6 +445,8 @@ ruff_macros::define_rule_mapping!(
|
||||
EM101 => rules::flake8_errmsg::rules::RawStringInException,
|
||||
EM102 => rules::flake8_errmsg::rules::FStringInException,
|
||||
EM103 => rules::flake8_errmsg::rules::DotFormatInException,
|
||||
// flake8-pyi
|
||||
PYI001 => rules::flake8_pyi::rules::PrefixTypeParams,
|
||||
// flake8-pytest-style
|
||||
PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
||||
PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
||||
@@ -627,6 +637,9 @@ pub enum Linter {
|
||||
/// [flake8-print](https://pypi.org/project/flake8-print/)
|
||||
#[prefix = "T20"]
|
||||
Flake8Print,
|
||||
/// [flake8-pyi](https://pypi.org/project/flake8-pyi/)
|
||||
#[prefix = "PYI"]
|
||||
Flake8Pyi,
|
||||
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
||||
#[prefix = "PT"]
|
||||
Flake8PytestStyle,
|
||||
@@ -742,12 +755,13 @@ impl Rule {
|
||||
| Rule::ShebangMissingExecutableFile
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNewline
|
||||
| Rule::BidirectionalUnicode
|
||||
| Rule::ShebangPython
|
||||
| Rule::ShebangWhitespace => &LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||
| Rule::AmbiguousUnicodeCharacterString
|
||||
| Rule::AvoidQuoteEscape
|
||||
| Rule::AvoidableEscapedQuote
|
||||
| Rule::BadQuotesDocstring
|
||||
| Rule::BadQuotesInlineString
|
||||
| Rule::BadQuotesMultilineString
|
||||
@@ -758,6 +772,10 @@ impl Rule {
|
||||
| Rule::SingleLineImplicitStringConcatenation
|
||||
| Rule::TrailingCommaMissing
|
||||
| Rule::TrailingCommaOnBareTupleProhibited
|
||||
| Rule::MultipleStatementsOnOneLineColon
|
||||
| Rule::UselessSemicolon
|
||||
| Rule::MultipleStatementsOnOneLineDef
|
||||
| Rule::MultipleStatementsOnOneLineSemicolon
|
||||
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
|
||||
Rule::IOError => &LintSource::Io,
|
||||
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
|
||||
@@ -843,6 +861,28 @@ mod tests {
|
||||
|
||||
use super::{Linter, Rule, RuleNamespace};
|
||||
|
||||
#[test]
|
||||
fn test_rule_naming_convention() {
|
||||
// The disallowed rule names are defined in a separate file so that they can also be picked up by add_rule.py.
|
||||
let patterns: Vec<_> = include_str!("../resources/test/disallowed_rule_names.txt")
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
glob::Pattern::new(line).expect("malformed pattern in disallowed_rule_names.txt")
|
||||
})
|
||||
.collect();
|
||||
|
||||
for rule in Rule::iter() {
|
||||
let rule_name = rule.as_ref();
|
||||
for pattern in &patterns {
|
||||
assert!(
|
||||
!pattern.matches(rule_name),
|
||||
"{rule_name} does not match naming convention, see CONTRIBUTING.md"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_code_serialization() {
|
||||
for rule in Rule::iter() {
|
||||
|
||||
@@ -10,14 +10,14 @@ use crate::source_code::Locator;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for commented-out Python code.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Commented-out code is dead code, and is often included inadvertently.
|
||||
/// It should be removed.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// # print('foo')
|
||||
/// ```
|
||||
|
||||
@@ -31,7 +31,7 @@ mod tests {
|
||||
Rule::MissingReturnTypeSpecialMethod,
|
||||
Rule::MissingReturnTypeStaticMethod,
|
||||
Rule::MissingReturnTypeClassMethod,
|
||||
Rule::DynamicallyTypedExpression,
|
||||
Rule::AnyType,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -59,7 +59,7 @@ mod tests {
|
||||
Rule::MissingReturnTypeSpecialMethod,
|
||||
Rule::MissingReturnTypeStaticMethod,
|
||||
Rule::MissingReturnTypeClassMethod,
|
||||
Rule::DynamicallyTypedExpression,
|
||||
Rule::AnyType,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -131,7 +131,7 @@ mod tests {
|
||||
Rule::MissingReturnTypeSpecialMethod,
|
||||
Rule::MissingReturnTypeStaticMethod,
|
||||
Rule::MissingReturnTypeClassMethod,
|
||||
Rule::DynamicallyTypedExpression,
|
||||
Rule::AnyType,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -148,7 +148,7 @@ mod tests {
|
||||
allow_star_arg_any: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::DynamicallyTypedExpression])
|
||||
..Settings::for_rules(vec![Rule::AnyType])
|
||||
},
|
||||
)?;
|
||||
assert_yaml_snapshot!(diagnostics);
|
||||
|
||||
@@ -16,6 +16,25 @@ use crate::visibility;
|
||||
use crate::visibility::Visibility;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that function arguments have type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the types of function arguments. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any provided arguments match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(x):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(x: int):
|
||||
/// ...
|
||||
/// ```
|
||||
pub struct MissingTypeFunctionArgument {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -29,6 +48,25 @@ impl Violation for MissingTypeFunctionArgument {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that function `*args` arguments have type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the types of function arguments. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any provided arguments match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(*args):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(*args: int):
|
||||
/// ...
|
||||
/// ```
|
||||
pub struct MissingTypeArgs {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -42,6 +80,25 @@ impl Violation for MissingTypeArgs {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that function `**kwargs` arguments have type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the types of function arguments. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any provided arguments match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(**kwargs):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(**kwargs: int):
|
||||
/// ...
|
||||
/// ```
|
||||
pub struct MissingTypeKwargs {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -55,6 +112,30 @@ impl Violation for MissingTypeKwargs {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that instance method `self` arguments have type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the types of function arguments. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any provided arguments match expectation.
|
||||
///
|
||||
/// Note that many type checkers will infer the type of `self` automatically, so this
|
||||
/// annotation is not strictly necessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def bar(self):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def bar(self: "Foo"):
|
||||
/// ...
|
||||
/// ```
|
||||
pub struct MissingTypeSelf {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -68,6 +149,32 @@ impl Violation for MissingTypeSelf {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that class method `cls` arguments have type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the types of function arguments. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any provided arguments match expectation.
|
||||
///
|
||||
/// Note that many type checkers will infer the type of `cls` automatically, so this
|
||||
/// annotation is not strictly necessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @classmethod
|
||||
/// def bar(cls):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @classmethod
|
||||
/// def bar(cls: Type["Foo"]):
|
||||
/// ...
|
||||
/// ```
|
||||
pub struct MissingTypeCls {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -81,6 +188,25 @@ impl Violation for MissingTypeCls {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that public functions and methods have return type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the return types of functions. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any returned values, and the types expected by callers, match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def add(a, b):
|
||||
/// return a + b
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
pub struct MissingReturnTypePublicFunction {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -94,6 +220,25 @@ impl Violation for MissingReturnTypePublicFunction {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that private functions and methods have return type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the return types of functions. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any returned values, and the types expected by callers, match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def _add(a, b):
|
||||
/// return a + b
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def _add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
pub struct MissingReturnTypePrivateFunction {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -107,6 +252,38 @@ impl Violation for MissingReturnTypePrivateFunction {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that "special" methods, like `__init__`, `__new__`, and `__call__`, have
|
||||
/// return type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the return types of functions. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any returned values, and the types expected by callers, match expectation.
|
||||
///
|
||||
/// Note that type checkers often allow you to omit the return type annotation for
|
||||
/// `__init__` methods, as long as at least one argument has a type annotation. To
|
||||
/// opt-in to this behavior, use the `mypy-init-return` setting in your `pyproject.toml`
|
||||
/// or `ruff.toml` file:
|
||||
///
|
||||
/// ```toml
|
||||
/// [tool.ruff.flake8-annotations]
|
||||
/// mypy-init-return = true
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __init__(self, x: int):
|
||||
/// self.x = x
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __init__(self, x: int) -> None:
|
||||
/// self.x = x
|
||||
/// ```
|
||||
pub struct MissingReturnTypeSpecialMethod {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -124,6 +301,29 @@ impl AlwaysAutofixableViolation for MissingReturnTypeSpecialMethod {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that static methods have return type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the return types of functions. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any returned values, and the types expected by callers, match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @staticmethod
|
||||
/// def bar():
|
||||
/// return 1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @staticmethod
|
||||
/// def bar() -> int:
|
||||
/// return 1
|
||||
/// ```
|
||||
pub struct MissingReturnTypeStaticMethod {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -137,6 +337,29 @@ impl Violation for MissingReturnTypeStaticMethod {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that class methods have return type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type annotations are a good way to document the return types of functions. They also
|
||||
/// help catch bugs, when used alongside a type checker, by ensuring that the types of
|
||||
/// any returned values, and the types expected by callers, match expectation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @classmethod
|
||||
/// def bar(cls):
|
||||
/// return 1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// @classmethod
|
||||
/// def bar(cls) -> int:
|
||||
/// return 1
|
||||
/// ```
|
||||
pub struct MissingReturnTypeClassMethod {
|
||||
pub name: String,
|
||||
}
|
||||
@@ -150,14 +373,42 @@ impl Violation for MissingReturnTypeClassMethod {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct DynamicallyTypedExpression {
|
||||
/// ## What it does
|
||||
/// Checks that an expression is annotated with a more specific type than
|
||||
/// `Any`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `Any` is a special type indicating an unconstrained type. When an
|
||||
/// expression is annotated with type `Any`, type checkers will allow all
|
||||
/// operations on it.
|
||||
///
|
||||
/// It's better to be explicit about the type of an expression, and to use
|
||||
/// `Any` as an "escape hatch" only when it is really needed.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(x: Any):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(x: int):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// * [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-any-type)
|
||||
/// * [`typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
|
||||
/// * [Mypy: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
|
||||
pub struct AnyType {
|
||||
pub name: String,
|
||||
}
|
||||
);
|
||||
impl Violation for DynamicallyTypedExpression {
|
||||
impl Violation for AnyType {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DynamicallyTypedExpression { name } = self;
|
||||
let AnyType { name } = self;
|
||||
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
|
||||
}
|
||||
}
|
||||
@@ -192,7 +443,7 @@ fn check_dynamically_typed<F>(
|
||||
{
|
||||
if checker.match_typing_expr(annotation, "Any") {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
DynamicallyTypedExpression { name: func() },
|
||||
AnyType { name: func() },
|
||||
Range::from_located(annotation),
|
||||
));
|
||||
};
|
||||
@@ -238,11 +489,7 @@ pub fn definition(
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::AnyType) {
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
annotation,
|
||||
@@ -275,11 +522,7 @@ pub fn definition(
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
@@ -310,11 +553,7 @@ pub fn definition(
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
@@ -372,11 +611,7 @@ pub fn definition(
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
has_typed_return = true;
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::AnyType) {
|
||||
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
||||
}
|
||||
} else if !(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_annotations/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_annotations/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: a
|
||||
location:
|
||||
row: 10
|
||||
@@ -14,7 +14,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: foo
|
||||
location:
|
||||
row: 15
|
||||
@@ -25,7 +25,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: a
|
||||
location:
|
||||
row: 40
|
||||
@@ -36,7 +36,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: foo_method
|
||||
location:
|
||||
row: 44
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_annotations/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_annotations/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -91,7 +91,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: a
|
||||
location:
|
||||
row: 44
|
||||
@@ -102,7 +102,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: foo
|
||||
location:
|
||||
row: 49
|
||||
@@ -113,7 +113,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "*args"
|
||||
location:
|
||||
row: 54
|
||||
@@ -124,7 +124,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "**kwargs"
|
||||
location:
|
||||
row: 54
|
||||
@@ -135,7 +135,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "*args"
|
||||
location:
|
||||
row: 59
|
||||
@@ -146,7 +146,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "**kwargs"
|
||||
location:
|
||||
row: 64
|
||||
@@ -168,7 +168,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: a
|
||||
location:
|
||||
row: 78
|
||||
@@ -179,7 +179,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: foo
|
||||
location:
|
||||
row: 82
|
||||
@@ -190,7 +190,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "*params"
|
||||
location:
|
||||
row: 86
|
||||
@@ -201,7 +201,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "**options"
|
||||
location:
|
||||
row: 86
|
||||
@@ -212,7 +212,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "*params"
|
||||
location:
|
||||
row: 90
|
||||
@@ -223,7 +223,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
DynamicallyTypedExpression:
|
||||
AnyType:
|
||||
name: "**options"
|
||||
location:
|
||||
row: 94
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
const PASSWORD_NAMES: [&str; 7] = [
|
||||
"password", "pass", "passwd", "pwd", "secret", "token", "secrete",
|
||||
];
|
||||
@@ -20,3 +22,21 @@ pub fn matches_password_name(string: &str) -> bool {
|
||||
.iter()
|
||||
.any(|name| string.to_lowercase().contains(name))
|
||||
}
|
||||
|
||||
pub fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
|
||||
type_.map_or(true, |type_| {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
elts.iter().any(|type_| {
|
||||
checker.resolve_call_path(type_).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["", "Exception"]
|
||||
|| call_path.as_slice() == ["", "BaseException"]
|
||||
})
|
||||
})
|
||||
} else {
|
||||
checker.resolve_call_path(type_).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["", "Exception"]
|
||||
|| call_path.as_slice() == ["", "BaseException"]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,13 +15,14 @@ mod tests {
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::AssertUsed, Path::new("S101.py"); "S101")]
|
||||
#[test_case(Rule::ExecUsed, Path::new("S102.py"); "S102")]
|
||||
#[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::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::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")]
|
||||
@@ -32,6 +33,7 @@ mod tests {
|
||||
#[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::TryExceptContinue, Path::new("S112.py"); "S112")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct AssertUsed;
|
||||
pub struct Assert;
|
||||
);
|
||||
impl Violation for AssertUsed {
|
||||
impl Violation for Assert {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `assert` detected")
|
||||
@@ -18,7 +18,7 @@ impl Violation for AssertUsed {
|
||||
/// S101
|
||||
pub fn assert_used(stmt: &Located<StmtKind>) -> Diagnostic {
|
||||
Diagnostic::new(
|
||||
AssertUsed,
|
||||
Assert,
|
||||
Range::new(stmt.location, stmt.location.with_col_offset("assert".len())),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct ExecUsed;
|
||||
pub struct ExecBuiltin;
|
||||
);
|
||||
impl Violation for ExecUsed {
|
||||
impl Violation for ExecBuiltin {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `exec` detected")
|
||||
@@ -23,5 +23,5 @@ pub fn exec_used(expr: &Expr, func: &Expr) -> Option<Diagnostic> {
|
||||
if id != "exec" {
|
||||
return None;
|
||||
}
|
||||
Some(Diagnostic::new(ExecUsed, Range::from_located(expr)))
|
||||
Some(Diagnostic::new(ExecBuiltin, Range::from_located(expr)))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Operator};
|
||||
|
||||
use super::super::helpers::string_literal;
|
||||
use crate::ast::helpers::{any_over_expr, unparse_expr};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
static SQL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)(select\s.*from\s|delete\s+from\s|insert\s+into\s.*values\s|update\s.*set\s)")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for strings that resemble SQL statements involved in some form
|
||||
/// string building operation.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// SQL injection is a common attack vector for web applications. Directly
|
||||
/// interpolating user input into SQL statements should always be avoided.
|
||||
/// Instead, favor parameterized queries, in which the SQL statement is
|
||||
/// provided separately from its parameters, as supported by `psycopg3`
|
||||
/// and other database drivers and ORMs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// query = "DELETE FROM foo WHERE id = '%s'" % identifier
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// * [B608: Test for SQL injection](https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html)
|
||||
/// * [psycopg3: Server-side binding](https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#server-side-binding)
|
||||
pub struct HardcodedSQLExpression {
|
||||
pub string: String,
|
||||
}
|
||||
);
|
||||
impl Violation for HardcodedSQLExpression {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let HardcodedSQLExpression { string } = self;
|
||||
format!(
|
||||
"Possible SQL injection vector through string-based query construction: \"{}\"",
|
||||
string.escape_debug()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_string_literal(expr: &Expr) -> bool {
|
||||
string_literal(expr).is_some()
|
||||
}
|
||||
|
||||
fn matches_sql_statement(string: &str) -> bool {
|
||||
SQL_REGEX.is_match(string)
|
||||
}
|
||||
|
||||
fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Option<String> {
|
||||
match &expr.node {
|
||||
// "select * from table where val = " + "str" + ...
|
||||
// "select * from table where val = %s" % ...
|
||||
ExprKind::BinOp {
|
||||
op: Operator::Add | Operator::Mod,
|
||||
..
|
||||
} => {
|
||||
let Some(parent) = checker.current_expr_parent() else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(unparse_expr(expr, checker.stylist));
|
||||
}
|
||||
return None;
|
||||
};
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
let ExprKind::BinOp { .. } = &parent.node else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(unparse_expr(expr, checker.stylist));
|
||||
}
|
||||
return None;
|
||||
};
|
||||
None
|
||||
}
|
||||
ExprKind::Call { func, .. } => {
|
||||
let ExprKind::Attribute{ attr, value, .. } = &func.node else {
|
||||
return None;
|
||||
};
|
||||
// "select * from table where val = {}".format(...)
|
||||
if attr == "format" && string_literal(value).is_some() {
|
||||
return Some(unparse_expr(expr, checker.stylist));
|
||||
};
|
||||
None
|
||||
}
|
||||
// f"select * from table where val = {val}"
|
||||
ExprKind::JoinedStr { .. } => Some(unparse_expr(expr, checker.stylist)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// S608
|
||||
pub fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
|
||||
match unparse_string_format_expression(checker, expr) {
|
||||
Some(string) if matches_sql_statement(&string) => {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedSQLExpression { string },
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
pub use assert_used::{assert_used, AssertUsed};
|
||||
pub use assert_used::{assert_used, Assert};
|
||||
pub use bad_file_permissions::{bad_file_permissions, BadFilePermissions};
|
||||
pub use exec_used::{exec_used, ExecUsed};
|
||||
pub use exec_used::{exec_used, ExecBuiltin};
|
||||
pub use hardcoded_bind_all_interfaces::{
|
||||
hardcoded_bind_all_interfaces, HardcodedBindAllInterfaces,
|
||||
};
|
||||
@@ -9,6 +9,7 @@ pub use hardcoded_password_func_arg::{hardcoded_password_func_arg, HardcodedPass
|
||||
pub use hardcoded_password_string::{
|
||||
assign_hardcoded_password_string, compare_to_hardcoded_password_string, HardcodedPasswordString,
|
||||
};
|
||||
pub use hardcoded_sql_expression::{hardcoded_sql_expression, HardcodedSQLExpression};
|
||||
pub use hardcoded_tmp_directory::{hardcoded_tmp_directory, HardcodedTempFile};
|
||||
pub use hashlib_insecure_hash_functions::{
|
||||
hashlib_insecure_hash_functions, HashlibInsecureHashFunction,
|
||||
@@ -23,6 +24,7 @@ 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 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};
|
||||
|
||||
@@ -33,6 +35,7 @@ mod hardcoded_bind_all_interfaces;
|
||||
mod hardcoded_password_default;
|
||||
mod hardcoded_password_func_arg;
|
||||
mod hardcoded_password_string;
|
||||
mod hardcoded_sql_expression;
|
||||
mod hardcoded_tmp_directory;
|
||||
mod hashlib_insecure_hash_functions;
|
||||
mod jinja2_autoescape_false;
|
||||
@@ -41,5 +44,6 @@ mod request_with_no_cert_validation;
|
||||
mod request_without_timeout;
|
||||
mod snmp_insecure_version;
|
||||
mod snmp_weak_cryptography;
|
||||
mod try_except_continue;
|
||||
mod try_except_pass;
|
||||
mod unsafe_yaml_load;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use rustpython_parser::ast::{Excepthandler, Expr, Stmt, StmtKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_bandit::helpers::is_untyped_exception;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct TryExceptContinue;
|
||||
);
|
||||
impl Violation for TryExceptContinue {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`try`-`except`-`continue` detected, consider logging the exception")
|
||||
}
|
||||
}
|
||||
|
||||
/// S112
|
||||
pub fn try_except_continue(
|
||||
checker: &mut Checker,
|
||||
excepthandler: &Excepthandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
check_typed_exception: bool,
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].node == StmtKind::Continue
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker))
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TryExceptContinue,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use rustpython_parser::ast::{Excepthandler, Expr, Stmt, StmtKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_bandit::helpers::is_untyped_exception;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
@@ -19,6 +21,7 @@ impl Violation for TryExceptPass {
|
||||
/// S110
|
||||
pub fn try_except_pass(
|
||||
checker: &mut Checker,
|
||||
excepthandler: &Excepthandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
@@ -26,17 +29,11 @@ pub fn try_except_pass(
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].node == StmtKind::Pass
|
||||
&& (check_typed_exception
|
||||
|| type_.map_or(true, |type_| {
|
||||
checker.resolve_call_path(type_).map_or(true, |call_path| {
|
||||
call_path.as_slice() == ["", "Exception"]
|
||||
|| call_path.as_slice() == ["", "BaseException"]
|
||||
})
|
||||
}))
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker))
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TryExceptPass,
|
||||
Range::from_located(&body[0]),
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_bandit/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
AssertUsed: ~
|
||||
Assert: ~
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
@@ -13,7 +13,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
AssertUsed: ~
|
||||
Assert: ~
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
@@ -23,7 +23,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
AssertUsed: ~
|
||||
Assert: ~
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_bandit/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ExecUsed: ~
|
||||
ExecBuiltin: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
@@ -13,7 +13,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ExecUsed: ~
|
||||
ExecBuiltin: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
source: src/rules/flake8_bandit/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TryExceptPass: ~
|
||||
location:
|
||||
row: 4
|
||||
column: 4
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 8
|
||||
@@ -15,8 +15,8 @@ expression: diagnostics
|
||||
- kind:
|
||||
TryExceptPass: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 8
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
source: src/rules/flake8_bandit/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TryExceptPass: ~
|
||||
location:
|
||||
row: 4
|
||||
column: 4
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 8
|
||||
@@ -15,8 +15,8 @@ expression: diagnostics
|
||||
- kind:
|
||||
TryExceptPass: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 8
|
||||
@@ -25,8 +25,8 @@ expression: diagnostics
|
||||
- kind:
|
||||
TryExceptPass: ~
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 8
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TryExceptContinue: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
TryExceptContinue: ~
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
TryExceptContinue: ~
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
TryExceptContinue: ~
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT %s FROM table\" % (var,)"
|
||||
location:
|
||||
row: 2
|
||||
column: 9
|
||||
end_location:
|
||||
row: 2
|
||||
column: 40
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT var FROM \" + table"
|
||||
location:
|
||||
row: 3
|
||||
column: 9
|
||||
end_location:
|
||||
row: 3
|
||||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT \" + val + \" FROM \" + table"
|
||||
location:
|
||||
row: 4
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 43
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT {} FROM table;\".format(var)"
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 44
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT * FROM table WHERE var = {var}\""
|
||||
location:
|
||||
row: 6
|
||||
column: 9
|
||||
end_location:
|
||||
row: 6
|
||||
column: 49
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE var = %s\" % (var,)"
|
||||
location:
|
||||
row: 8
|
||||
column: 9
|
||||
end_location:
|
||||
row: 8
|
||||
column: 52
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE VAR = \" + var"
|
||||
location:
|
||||
row: 9
|
||||
column: 9
|
||||
end_location:
|
||||
row: 9
|
||||
column: 47
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM \" + table + \"WHERE var = \" + var"
|
||||
location:
|
||||
row: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 10
|
||||
column: 54
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE var = {}\".format(var)"
|
||||
location:
|
||||
row: 11
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 55
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"DELETE FROM table WHERE var = {var}\""
|
||||
location:
|
||||
row: 12
|
||||
column: 10
|
||||
end_location:
|
||||
row: 12
|
||||
column: 48
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO table VALUES (%s)\" % (var,)"
|
||||
location:
|
||||
row: 14
|
||||
column: 10
|
||||
end_location:
|
||||
row: 14
|
||||
column: 50
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO TABLE VALUES (\" + var + \")\""
|
||||
location:
|
||||
row: 15
|
||||
column: 10
|
||||
end_location:
|
||||
row: 15
|
||||
column: 50
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO {} VALUES ({})\".format(table, var)"
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
end_location:
|
||||
row: 16
|
||||
column: 57
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"INSERT INTO {table} VALUES var = {var}\""
|
||||
location:
|
||||
row: 17
|
||||
column: 10
|
||||
end_location:
|
||||
row: 17
|
||||
column: 51
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE %s SET var = %s\" % (table, var)"
|
||||
location:
|
||||
row: 19
|
||||
column: 10
|
||||
end_location:
|
||||
row: 19
|
||||
column: 49
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE \" + table + \" SET var = \" + var"
|
||||
location:
|
||||
row: 20
|
||||
column: 10
|
||||
end_location:
|
||||
row: 20
|
||||
column: 49
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE {} SET var = {}\".format(table, var)"
|
||||
location:
|
||||
row: 21
|
||||
column: 10
|
||||
end_location:
|
||||
row: 21
|
||||
column: 53
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"UPDATE {table} SET var = {var}\""
|
||||
location:
|
||||
row: 22
|
||||
column: 10
|
||||
end_location:
|
||||
row: 22
|
||||
column: 43
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select %s from table\" % (var,)"
|
||||
location:
|
||||
row: 24
|
||||
column: 10
|
||||
end_location:
|
||||
row: 24
|
||||
column: 41
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select var from \" + table"
|
||||
location:
|
||||
row: 25
|
||||
column: 10
|
||||
end_location:
|
||||
row: 25
|
||||
column: 36
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select \" + val + \" from \" + table"
|
||||
location:
|
||||
row: 26
|
||||
column: 10
|
||||
end_location:
|
||||
row: 26
|
||||
column: 44
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select {} from table;\".format(var)"
|
||||
location:
|
||||
row: 27
|
||||
column: 10
|
||||
end_location:
|
||||
row: 27
|
||||
column: 45
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"select * from table where var = {var}\""
|
||||
location:
|
||||
row: 28
|
||||
column: 10
|
||||
end_location:
|
||||
row: 28
|
||||
column: 50
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = %s\" % (var,)"
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
end_location:
|
||||
row: 30
|
||||
column: 53
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = \" + var"
|
||||
location:
|
||||
row: 31
|
||||
column: 10
|
||||
end_location:
|
||||
row: 31
|
||||
column: 48
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from \" + table + \"where var = \" + var"
|
||||
location:
|
||||
row: 32
|
||||
column: 10
|
||||
end_location:
|
||||
row: 32
|
||||
column: 55
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = {}\".format(var)"
|
||||
location:
|
||||
row: 33
|
||||
column: 10
|
||||
end_location:
|
||||
row: 33
|
||||
column: 56
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"delete from table where var = {var}\""
|
||||
location:
|
||||
row: 34
|
||||
column: 10
|
||||
end_location:
|
||||
row: 34
|
||||
column: 48
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into table values (%s)\" % (var,)"
|
||||
location:
|
||||
row: 36
|
||||
column: 10
|
||||
end_location:
|
||||
row: 36
|
||||
column: 50
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into table values (\" + var + \")\""
|
||||
location:
|
||||
row: 37
|
||||
column: 10
|
||||
end_location:
|
||||
row: 37
|
||||
column: 50
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into {} values ({})\".format(table, var)"
|
||||
location:
|
||||
row: 38
|
||||
column: 10
|
||||
end_location:
|
||||
row: 38
|
||||
column: 57
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"insert into {table} values var = {var}\""
|
||||
location:
|
||||
row: 39
|
||||
column: 10
|
||||
end_location:
|
||||
row: 39
|
||||
column: 51
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update %s set var = %s\" % (table, var)"
|
||||
location:
|
||||
row: 41
|
||||
column: 10
|
||||
end_location:
|
||||
row: 41
|
||||
column: 49
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update \" + table + \" set var = \" + var"
|
||||
location:
|
||||
row: 42
|
||||
column: 10
|
||||
end_location:
|
||||
row: 42
|
||||
column: 49
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update {} set var = {}\".format(table, var)"
|
||||
location:
|
||||
row: 43
|
||||
column: 10
|
||||
end_location:
|
||||
row: 43
|
||||
column: 53
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"update {table} set var = {var}\""
|
||||
location:
|
||||
row: 44
|
||||
column: 10
|
||||
end_location:
|
||||
row: 44
|
||||
column: 43
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM table\\n WHERE var = %s\\n \" % var"
|
||||
location:
|
||||
row: 48
|
||||
column: 11
|
||||
end_location:
|
||||
row: 52
|
||||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM TABLE\\n WHERE var =\\n \" + var"
|
||||
location:
|
||||
row: 55
|
||||
column: 11
|
||||
end_location:
|
||||
row: 59
|
||||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM table\\n WHERE var = {}\\n \".format(var)"
|
||||
location:
|
||||
row: 62
|
||||
column: 11
|
||||
end_location:
|
||||
row: 66
|
||||
column: 19
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"\\n SELECT *\\n FROM table\\n WHERE var = {var}\\n \""
|
||||
location:
|
||||
row: 69
|
||||
column: 11
|
||||
end_location:
|
||||
row: 73
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT *FROM tableWHERE var = {var}\""
|
||||
location:
|
||||
row: 77
|
||||
column: 8
|
||||
end_location:
|
||||
row: 79
|
||||
column: 28
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = %s\" % var"
|
||||
location:
|
||||
row: 83
|
||||
column: 25
|
||||
end_location:
|
||||
row: 83
|
||||
column: 67
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT * FROM table WHERE var = {var}\""
|
||||
location:
|
||||
row: 84
|
||||
column: 25
|
||||
end_location:
|
||||
row: 84
|
||||
column: 65
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = {}\".format(var)"
|
||||
location:
|
||||
row: 85
|
||||
column: 25
|
||||
end_location:
|
||||
row: 85
|
||||
column: 73
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = %s\" % var"
|
||||
location:
|
||||
row: 86
|
||||
column: 29
|
||||
end_location:
|
||||
row: 86
|
||||
column: 71
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -23,7 +23,7 @@ mod tests {
|
||||
#[test_case(Rule::FunctionCallArgumentDefault, 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::DoNotAssertFalse, Path::new("B011.py"); "B011")]
|
||||
#[test_case(Rule::AssertFalse, Path::new("B011.py"); "B011")]
|
||||
#[test_case(Rule::JumpStatementInFinally, Path::new("B012.py"); "B012")]
|
||||
#[test_case(Rule::RedundantTupleInExceptionHandler, Path::new("B013.py"); "B013")]
|
||||
#[test_case(Rule::DuplicateHandlerException, Path::new("B014.py"); "B014")]
|
||||
|
||||
@@ -9,9 +9,9 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
pub struct DoNotAssertFalse;
|
||||
pub struct AssertFalse;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for DoNotAssertFalse {
|
||||
impl AlwaysAutofixableViolation for AssertFalse {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`")
|
||||
@@ -61,7 +61,7 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(DoNotAssertFalse, Range::from_located(test));
|
||||
let mut diagnostic = Diagnostic::new(AssertFalse, Range::from_located(test));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(Fix::replacement(
|
||||
unparse_stmt(&assertion_error(msg), checker.stylist),
|
||||
|
||||
@@ -7,17 +7,17 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for `self.assertRaises(Exception)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// `assertRaises(Exception)` can lead to your test passing even if the
|
||||
/// code being tested is never executed due to a typo.
|
||||
///
|
||||
/// Either assert for a more specific exception (builtin or custom), use
|
||||
/// `assertRaisesRegex` or the context manager form of `assertRaises`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// self.assertRaises(Exception, foo)
|
||||
/// ```
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use abstract_base_class::{
|
||||
abstract_base_class, AbstractBaseClassWithoutAbstractMethod,
|
||||
EmptyMethodWithoutAbstractDecorator,
|
||||
};
|
||||
pub use assert_false::{assert_false, DoNotAssertFalse};
|
||||
pub use assert_false::{assert_false, AssertFalse};
|
||||
pub use assert_raises_exception::{assert_raises_exception, AssertRaisesException};
|
||||
pub use assignment_to_os_environ::{assignment_to_os_environ, AssignmentToOsEnviron};
|
||||
pub use cached_instance_method::{cached_instance_method, CachedInstanceMethod};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_bugbear/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
DoNotAssertFalse: ~
|
||||
AssertFalse: ~
|
||||
location:
|
||||
row: 8
|
||||
column: 7
|
||||
@@ -21,7 +21,7 @@ expression: diagnostics
|
||||
column: 12
|
||||
parent: ~
|
||||
- kind:
|
||||
DoNotAssertFalse: ~
|
||||
AssertFalse: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 7
|
||||
|
||||
@@ -7,11 +7,11 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for imports that are typically imported using a common convention,
|
||||
/// like `import pandas as pd`, and enforces that convention.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use a common convention for imports to make your code
|
||||
/// more readable and idiomatic.
|
||||
///
|
||||
@@ -19,7 +19,7 @@ define_violation!(
|
||||
/// convention for importing the `pandas` library, and users typically expect
|
||||
/// Pandas to be aliased as `pd`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pandas
|
||||
/// ```
|
||||
|
||||
@@ -8,29 +8,34 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for packages that are missing an `__init__.py` file.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Python packages are directories that contain a file named `__init__.py`.
|
||||
/// The existence of this file indicates that the directory is a Python
|
||||
/// package, and so it can be imported the same way a module can be
|
||||
/// imported.
|
||||
///
|
||||
/// Directories that lack an `__init__.py` file can still be imported, but
|
||||
/// they're indicative of a special kind of package, known as a namespace
|
||||
/// package (see: [PEP 420](https://www.python.org/dev/peps/pep-0420/)).
|
||||
/// they're indicative of a special kind of package, known as a "namespace
|
||||
/// package" (see: [PEP 420](https://www.python.org/dev/peps/pep-0420/)).
|
||||
/// Namespace packages are less widely used, so a package that lacks an
|
||||
/// `__init__.py` file is typically meant to be a regular package, and
|
||||
/// the absence of the `__init__.py` file is probably an oversight.
|
||||
///
|
||||
/// Namespace packages are a relatively new feature of Python, and they're
|
||||
/// not widely used. So a package that lacks an `__init__.py` file is
|
||||
/// typically meant to be a regular package, and the absence of the
|
||||
/// `__init__.py` file is probably an oversight.
|
||||
pub struct ImplicitNamespacePackage(pub String);
|
||||
/// Note that namespace packages can be specified via the
|
||||
/// [`namespace-packages`](https://github.com/charliermarsh/ruff#namespace-packages)
|
||||
/// configuration option. Adding a namespace package to the configuration
|
||||
/// will suppress this violation for a given package.
|
||||
pub struct ImplicitNamespacePackage {
|
||||
pub filename: String,
|
||||
}
|
||||
);
|
||||
impl Violation for ImplicitNamespacePackage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ImplicitNamespacePackage(filename) = self;
|
||||
let ImplicitNamespacePackage { filename } = self;
|
||||
format!("File `{filename}` is part of an implicit namespace package. Add an `__init__.py`.")
|
||||
}
|
||||
}
|
||||
@@ -59,7 +64,9 @@ pub fn implicit_namespace_package(
|
||||
.to_string_lossy()
|
||||
.replace(std::path::MAIN_SEPARATOR, "/"); // The snapshot test expects / as the path separator.
|
||||
Some(Diagnostic::new(
|
||||
ImplicitNamespacePackage(fs::relativize_path(path)),
|
||||
ImplicitNamespacePackage {
|
||||
filename: fs::relativize_path(path),
|
||||
},
|
||||
Range::default(),
|
||||
))
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,8 @@ source: crates/ruff/src/rules/flake8_no_pep420/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_empty/example.py"
|
||||
ImplicitNamespacePackage:
|
||||
filename: "./resources/test/fixtures/flake8_no_pep420/test_fail_empty/example.py"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
|
||||
@@ -3,7 +3,8 @@ source: crates/ruff/src/rules/flake8_no_pep420/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_nonempty/example.py"
|
||||
ImplicitNamespacePackage:
|
||||
filename: "./resources/test/fixtures/flake8_no_pep420/test_fail_nonempty/example.py"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
|
||||
@@ -3,7 +3,8 @@ source: crates/ruff/src/rules/flake8_no_pep420/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_shebang/example.py"
|
||||
ImplicitNamespacePackage:
|
||||
filename: "./resources/test/fixtures/flake8_no_pep420/test_fail_shebang/example.py"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
|
||||
26
crates/ruff/src/rules/flake8_pyi/mod.rs
Normal file
26
crates/ruff/src/rules/flake8_pyi/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
//! Rules from [flake8-pyi](https://pypi.org/project/flake8-pyi/).
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_yaml_snapshot, settings};
|
||||
|
||||
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.pyi"))]
|
||||
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_pyi").join(path).as_path(),
|
||||
&settings::Settings::for_rule(rule_code),
|
||||
)?;
|
||||
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
92
crates/ruff/src/rules/flake8_pyi/rules.rs
Normal file
92
crates/ruff/src/rules/flake8_pyi/rules.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum VarKind {
|
||||
TypeVar,
|
||||
ParamSpec,
|
||||
TypeVarTuple,
|
||||
}
|
||||
|
||||
impl fmt::Display for VarKind {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
VarKind::TypeVar => fmt.write_str("TypeVar"),
|
||||
VarKind::ParamSpec => fmt.write_str("ParamSpec"),
|
||||
VarKind::TypeVarTuple => fmt.write_str("TypeVarTuple"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that type `TypeVar`, `ParamSpec`, and `TypeVarTuple` definitions in
|
||||
/// stubs are prefixed with `_`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By prefixing type parameters with `_`, we can avoid accidentally exposing
|
||||
/// names internal to the stub.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import TypeVar
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import TypeVar
|
||||
///
|
||||
/// _T = TypeVar("_T")
|
||||
/// ```
|
||||
pub struct PrefixTypeParams {
|
||||
pub kind: VarKind,
|
||||
}
|
||||
);
|
||||
impl Violation for PrefixTypeParams {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let PrefixTypeParams { kind } = self;
|
||||
format!("Name of private `{kind}` must start with _")
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI001
|
||||
pub fn prefix_type_params(checker: &mut Checker, value: &Expr, targets: &[Expr]) {
|
||||
if targets.len() != 1 {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Name { id, .. } = &targets[0].node {
|
||||
if id.starts_with('_') {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let ExprKind::Call { func, .. } = &value.node {
|
||||
let Some(kind) = checker.resolve_call_path(func).and_then(|call_path| {
|
||||
if checker.match_typing_call_path(&call_path, "ParamSpec") {
|
||||
Some(VarKind::ParamSpec)
|
||||
} else if checker.match_typing_call_path(&call_path, "TypeVar") {
|
||||
Some(VarKind::TypeVar)
|
||||
} else if checker.match_typing_call_path(&call_path, "TypeVarTuple") {
|
||||
Some(VarKind::TypeVarTuple)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
PrefixTypeParams { kind },
|
||||
Range::from_located(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
PrefixTypeParams:
|
||||
kind: TypeVar
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrefixTypeParams:
|
||||
kind: TypeVarTuple
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 31
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrefixTypeParams:
|
||||
kind: ParamSpec
|
||||
location:
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 7
|
||||
column: 18
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
use num_traits::identities::Zero;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use crate::ast::helpers::collect_call_path;
|
||||
use crate::ast::helpers::{collect_call_path, map_callable};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
const ITERABLE_INITIALIZERS: &[&str] = &["dict", "frozenset", "list", "tuple", "set"];
|
||||
|
||||
/// Given a decorators that can be used with or without explicit call syntax, return
|
||||
/// the underlying callable.
|
||||
fn callable_decorator(decorator: &Expr) -> &Expr {
|
||||
if let ExprKind::Call { func, .. } = &decorator.node {
|
||||
func
|
||||
} else {
|
||||
decorator
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mark_decorators(decorators: &[Expr]) -> impl Iterator<Item = &Expr> {
|
||||
decorators
|
||||
.iter()
|
||||
@@ -23,9 +13,7 @@ pub fn get_mark_decorators(decorators: &[Expr]) -> impl Iterator<Item = &Expr> {
|
||||
}
|
||||
|
||||
pub fn get_mark_name(decorator: &Expr) -> &str {
|
||||
collect_call_path(callable_decorator(decorator))
|
||||
.last()
|
||||
.unwrap()
|
||||
collect_call_path(map_callable(decorator)).last().unwrap()
|
||||
}
|
||||
|
||||
pub fn is_pytest_fail(call: &Expr, checker: &Checker) -> bool {
|
||||
@@ -47,7 +35,7 @@ pub fn is_pytest_fixture(decorator: &Expr, checker: &Checker) -> bool {
|
||||
}
|
||||
|
||||
pub fn is_pytest_mark(decorator: &Expr) -> bool {
|
||||
let segments = collect_call_path(callable_decorator(decorator));
|
||||
let segments = collect_call_path(map_callable(decorator));
|
||||
if segments.len() > 2 {
|
||||
segments[0] == "pytest" && segments[1] == "mark"
|
||||
} else {
|
||||
@@ -57,7 +45,7 @@ pub fn is_pytest_mark(decorator: &Expr) -> bool {
|
||||
|
||||
pub fn is_pytest_yield_fixture(decorator: &Expr, checker: &Checker) -> bool {
|
||||
checker
|
||||
.resolve_call_path(callable_decorator(decorator))
|
||||
.resolve_call_path(map_callable(decorator))
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["pytest", "yield_fixture"]
|
||||
})
|
||||
@@ -115,7 +103,7 @@ pub fn is_falsy_constant(expr: &Expr) -> bool {
|
||||
|
||||
pub fn is_pytest_parametrize(decorator: &Expr, checker: &Checker) -> bool {
|
||||
checker
|
||||
.resolve_call_path(callable_decorator(decorator))
|
||||
.resolve_call_path(map_callable(decorator))
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["pytest", "mark", "parametrize"]
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@ mod tests {
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidQuoteEscape,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -65,7 +65,7 @@ mod tests {
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidQuoteEscape,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidQuoteEscape,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
@@ -131,7 +131,7 @@ mod tests {
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidQuoteEscape,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -12,16 +12,16 @@ use crate::source_code::Locator;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for inline strings that use single quotes or double quotes,
|
||||
/// depending on the value of the [`inline-quotes`](https://github.com/charliermarsh/ruff#inline-quotes)
|
||||
/// setting.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for inline
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = 'bar'
|
||||
/// ```
|
||||
@@ -54,16 +54,16 @@ impl AlwaysAutofixableViolation for BadQuotesInlineString {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for multiline strings that use single quotes or double quotes,
|
||||
/// depending on the value of the [`multiline-quotes`](https://github.com/charliermarsh/ruff#multiline-quotes)
|
||||
/// setting.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for multiline
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = '''
|
||||
/// bar
|
||||
@@ -100,15 +100,15 @@ impl AlwaysAutofixableViolation for BadQuotesMultilineString {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for docstrings that use single quotes or double quotes, depending on the value of the [`docstring-quotes`](https://github.com/charliermarsh/ruff#docstring-quotes)
|
||||
/// setting.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for docstring
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// '''
|
||||
/// bar
|
||||
@@ -145,15 +145,15 @@ impl AlwaysAutofixableViolation for BadQuotesDocstring {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// ## What it does
|
||||
/// Checks for strings that include escaped quotes, and suggests changing
|
||||
/// the quote style to avoid the need to escape them.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// ## Why is this bad?
|
||||
/// It's preferable to avoid escaped quotes in strings. By changing the
|
||||
/// outer quote style, you can avoid escaping inner quotes.
|
||||
///
|
||||
/// ### Example
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = 'bar\'s'
|
||||
/// ```
|
||||
@@ -162,9 +162,9 @@ define_violation!(
|
||||
/// ```python
|
||||
/// foo = "bar's"
|
||||
/// ```
|
||||
pub struct AvoidQuoteEscape;
|
||||
pub struct AvoidableEscapedQuote;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for AvoidQuoteEscape {
|
||||
impl AlwaysAutofixableViolation for AvoidableEscapedQuote {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Change outer quotes to avoid escaping inner quotes")
|
||||
@@ -379,9 +379,9 @@ fn strings(
|
||||
&& !string_contents.contains(bad_single("es_settings.inline_quotes))
|
||||
{
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(AvoidQuoteEscape, Range::new(*start, *end));
|
||||
Diagnostic::new(AvoidableEscapedQuote, Range::new(*start, *end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::AvoidQuoteEscape)
|
||||
&& settings.rules.should_fix(&Rule::AvoidableEscapedQuote)
|
||||
{
|
||||
let quote = bad_single("es_settings.inline_quotes);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_quotes/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_quotes/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
AvoidQuoteEscape: ~
|
||||
AvoidableEscapedQuote: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 25
|
||||
@@ -21,7 +21,7 @@ expression: diagnostics
|
||||
column: 47
|
||||
parent: ~
|
||||
- kind:
|
||||
AvoidQuoteEscape: ~
|
||||
AvoidableEscapedQuote: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_quotes/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_quotes/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
AvoidQuoteEscape: ~
|
||||
AvoidableEscapedQuote: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 25
|
||||
@@ -21,7 +21,7 @@ expression: diagnostics
|
||||
column: 47
|
||||
parent: ~
|
||||
- kind:
|
||||
AvoidQuoteEscape: ~
|
||||
AvoidableEscapedQuote: ~
|
||||
location:
|
||||
row: 2
|
||||
column: 25
|
||||
@@ -39,7 +39,7 @@ expression: diagnostics
|
||||
column: 52
|
||||
parent: ~
|
||||
- kind:
|
||||
AvoidQuoteEscape: ~
|
||||
AvoidableEscapedQuote: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 4
|
||||
|
||||
@@ -165,6 +165,36 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
|
||||
}
|
||||
}
|
||||
|
||||
const NORETURN_FUNCS: &[&[&str]] = &[
|
||||
// builtins
|
||||
&["", "exit"],
|
||||
&["", "quit"],
|
||||
// stdlib
|
||||
&["builtins", "exit"],
|
||||
&["builtins", "quit"],
|
||||
&["os", "_exit"],
|
||||
&["os", "abort"],
|
||||
&["posix", "_exit"],
|
||||
&["posix", "abort"],
|
||||
&["sys", "exit"],
|
||||
&["_thread", "exit"],
|
||||
&["_winapi", "ExitProcess"],
|
||||
// third-party modules
|
||||
&["pytest", "exit"],
|
||||
&["pytest", "fail"],
|
||||
&["pytest", "skip"],
|
||||
&["pytest", "xfail"],
|
||||
];
|
||||
|
||||
/// Return `true` if the `func` is a known function that never returns.
|
||||
fn is_noreturn_func(checker: &Checker, func: &Expr) -> bool {
|
||||
checker.resolve_call_path(func).map_or(false, |call_path| {
|
||||
NORETURN_FUNCS
|
||||
.iter()
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
})
|
||||
}
|
||||
|
||||
/// RET503
|
||||
fn implicit_return(checker: &mut Checker, last_stmt: &Stmt) {
|
||||
match &last_stmt.node {
|
||||
@@ -208,6 +238,12 @@ fn implicit_return(checker: &mut Checker, last_stmt: &Stmt) {
|
||||
| StmtKind::While { .. }
|
||||
| StmtKind::Raise { .. }
|
||||
| StmtKind::Try { .. } => {}
|
||||
StmtKind::Expr { value, .. }
|
||||
if matches!(
|
||||
&value.node,
|
||||
ExprKind::Call { func, .. }
|
||||
if is_noreturn_func(checker, func)
|
||||
) => {}
|
||||
_ => {
|
||||
let mut diagnostic = Diagnostic::new(ImplicitReturn, Range::from_located(last_stmt));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
|
||||
@@ -1,82 +1,120 @@
|
||||
---
|
||||
source: src/rules/flake8_return/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_return/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 7
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
row: 19
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 14
|
||||
row: 25
|
||||
column: 8
|
||||
end_location:
|
||||
row: 14
|
||||
row: 25
|
||||
column: 15
|
||||
fix:
|
||||
content:
|
||||
- " return None"
|
||||
- ""
|
||||
location:
|
||||
row: 15
|
||||
row: 26
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
row: 26
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 23
|
||||
row: 34
|
||||
column: 4
|
||||
end_location:
|
||||
row: 23
|
||||
row: 34
|
||||
column: 11
|
||||
fix:
|
||||
content:
|
||||
- " return None"
|
||||
- ""
|
||||
location:
|
||||
row: 24
|
||||
row: 35
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
row: 35
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 29
|
||||
row: 40
|
||||
column: 8
|
||||
end_location:
|
||||
row: 30
|
||||
row: 41
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 39
|
||||
row: 50
|
||||
column: 8
|
||||
end_location:
|
||||
row: 39
|
||||
row: 50
|
||||
column: 15
|
||||
fix:
|
||||
content:
|
||||
- " return None"
|
||||
- ""
|
||||
location:
|
||||
row: 40
|
||||
row: 51
|
||||
column: 0
|
||||
end_location:
|
||||
row: 40
|
||||
row: 51
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
column: 22
|
||||
fix:
|
||||
content:
|
||||
- " return None"
|
||||
- ""
|
||||
location:
|
||||
row: 58
|
||||
column: 0
|
||||
end_location:
|
||||
row: 58
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 64
|
||||
column: 4
|
||||
end_location:
|
||||
row: 64
|
||||
column: 21
|
||||
fix:
|
||||
content:
|
||||
- " return None"
|
||||
- ""
|
||||
location:
|
||||
row: 65
|
||||
column: 0
|
||||
end_location:
|
||||
row: 65
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::helpers::collect_call_path;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
@@ -25,20 +27,17 @@ const VALID_IDS: [&str; 3] = ["self", "cls", "mcs"];
|
||||
pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
|
||||
if !attr.ends_with("__") && (attr.starts_with('_') || attr.starts_with("__")) {
|
||||
let id = match &value.node {
|
||||
ExprKind::Name { id, .. } => id,
|
||||
ExprKind::Attribute { attr, .. } => attr,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !VALID_IDS.contains(&id.as_str()) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
PrivateMemberAccess {
|
||||
access: format!("{}.{}", id, attr),
|
||||
},
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let call_path = collect_call_path(value);
|
||||
if VALID_IDS.iter().any(|id| call_path.as_slice() == [*id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
PrivateMemberAccess {
|
||||
access: attr.to_string(),
|
||||
},
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/rules/flake8_self/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_self/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: bar._private
|
||||
access: _private
|
||||
location:
|
||||
row: 34
|
||||
column: 11
|
||||
@@ -15,57 +15,79 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: foo._private_thing
|
||||
access: _private
|
||||
location:
|
||||
row: 55
|
||||
row: 36
|
||||
column: 11
|
||||
end_location:
|
||||
row: 36
|
||||
column: 30
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: _private_thing
|
||||
location:
|
||||
row: 58
|
||||
column: 6
|
||||
end_location:
|
||||
row: 55
|
||||
row: 58
|
||||
column: 24
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: foo.__really_private_thing
|
||||
access: __really_private_thing
|
||||
location:
|
||||
row: 56
|
||||
row: 59
|
||||
column: 6
|
||||
end_location:
|
||||
row: 56
|
||||
row: 59
|
||||
column: 32
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: foo._private_func
|
||||
access: _private_func
|
||||
location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 6
|
||||
end_location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: foo.__really_private_func
|
||||
access: __really_private_func
|
||||
location:
|
||||
row: 58
|
||||
row: 61
|
||||
column: 6
|
||||
end_location:
|
||||
row: 58
|
||||
row: 61
|
||||
column: 31
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: bar._private
|
||||
access: _private
|
||||
location:
|
||||
row: 59
|
||||
row: 62
|
||||
column: 6
|
||||
end_location:
|
||||
row: 59
|
||||
row: 62
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
PrivateMemberAccess:
|
||||
access: _private_thing
|
||||
location:
|
||||
row: 63
|
||||
column: 6
|
||||
end_location:
|
||||
row: 63
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use log::error;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::comparable::ComparableExpr;
|
||||
use crate::ast::helpers::{
|
||||
contains_call_path, contains_effect, create_expr, create_stmt, first_colon_range, has_comments,
|
||||
@@ -12,44 +13,62 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_simplify::rules::fix_if;
|
||||
use crate::violation::{AlwaysAutofixableViolation, Availability, Violation};
|
||||
use crate::AutofixKind;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
define_violation!(
|
||||
pub struct NestedIfStatements;
|
||||
pub struct NestedIfStatements {
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for NestedIfStatements {
|
||||
impl Violation for NestedIfStatements {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use a single `if` statement instead of nested `if` statements")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
"Combine `if` statements using `and`".to_string()
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let NestedIfStatements { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|_| format!("Combine `if` statements using `and`"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct ReturnBoolConditionDirectly {
|
||||
pub cond: String,
|
||||
pub condition: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for ReturnBoolConditionDirectly {
|
||||
impl Violation for ReturnBoolConditionDirectly {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ReturnBoolConditionDirectly { cond } = self;
|
||||
format!("Return the condition `{cond}` directly")
|
||||
let ReturnBoolConditionDirectly { condition, .. } = self;
|
||||
format!("Return the condition `{condition}` directly")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let ReturnBoolConditionDirectly { cond } = self;
|
||||
format!("Replace with `return {cond}`")
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let ReturnBoolConditionDirectly { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|ReturnBoolConditionDirectly { condition, .. }| {
|
||||
format!("Replace with `return {condition}`")
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct UseTernaryOperator {
|
||||
pub contents: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl Violation for UseTernaryOperator {
|
||||
@@ -57,30 +76,44 @@ impl Violation for UseTernaryOperator {
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UseTernaryOperator { contents } = self;
|
||||
let UseTernaryOperator { contents, .. } = self;
|
||||
format!("Use ternary operator `{contents}` instead of if-else-block")
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
Some(|UseTernaryOperator { contents }| format!("Replace if-else-block with `{contents}`"))
|
||||
let UseTernaryOperator { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|UseTernaryOperator { contents, .. }| {
|
||||
format!("Replace if-else-block with `{contents}`")
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct DictGetWithDefault {
|
||||
pub contents: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for DictGetWithDefault {
|
||||
impl Violation for DictGetWithDefault {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DictGetWithDefault { contents } = self;
|
||||
let DictGetWithDefault { contents, .. } = self;
|
||||
format!("Use `{contents}` instead of an `if` block")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let DictGetWithDefault { contents } = self;
|
||||
format!("Replace with `{contents}`")
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let DictGetWithDefault { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|DictGetWithDefault { contents, .. }| format!("Replace with `{contents}`"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,37 +196,39 @@ pub fn nested_if_statements(
|
||||
let Some((test, first_stmt)) = find_last_nested_if(body) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let colon = first_colon_range(
|
||||
Range::new(test.end_location.unwrap(), first_stmt.location),
|
||||
checker.locator,
|
||||
);
|
||||
|
||||
// The fixer preserves comments in the nested body, but removes comments between
|
||||
// the outer and inner if statements.
|
||||
let nested_if = &body[0];
|
||||
let fixable = !has_comments_in(
|
||||
Range::new(stmt.location, nested_if.location),
|
||||
checker.locator,
|
||||
);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NestedIfStatements,
|
||||
NestedIfStatements { fixable },
|
||||
colon.map_or_else(
|
||||
|| Range::from_located(stmt),
|
||||
|colon| Range::new(stmt.location, colon.end_location),
|
||||
),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// The fixer preserves comments in the nested body, but removes comments between
|
||||
// the outer and inner if statements.
|
||||
let nested_if = &body[0];
|
||||
if !has_comments_in(
|
||||
Range::new(stmt.location, nested_if.location),
|
||||
checker.locator,
|
||||
) {
|
||||
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, stmt) {
|
||||
Ok(fix) => {
|
||||
if fix
|
||||
.content
|
||||
.lines()
|
||||
.all(|line| line.len() <= checker.settings.line_length)
|
||||
{
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, stmt) {
|
||||
Ok(fix) => {
|
||||
if fix
|
||||
.content
|
||||
.lines()
|
||||
.all(|line| line.len() <= checker.settings.line_length)
|
||||
{
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(err) => error!("Failed to fix nested if: {err}"),
|
||||
}
|
||||
Err(err) => error!("Failed to fix nested if: {err}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -247,16 +282,18 @@ pub fn return_bool_condition_directly(checker: &mut Checker, stmt: &Stmt) {
|
||||
}
|
||||
|
||||
let condition = unparse_expr(test, checker.stylist);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ReturnBoolConditionDirectly { cond: condition },
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& matches!(if_return, Bool::True)
|
||||
let fixable = matches!(if_return, Bool::True)
|
||||
&& matches!(else_return, Bool::False)
|
||||
&& !has_comments(stmt, checker.locator)
|
||||
{
|
||||
&& (matches!(test.node, ExprKind::Compare { .. }) || checker.is_builtin("bool"));
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ReturnBoolConditionDirectly { condition, fixable },
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
if matches!(test.node, ExprKind::Compare { .. }) {
|
||||
// If the condition is a comparison, we can replace it with the condition.
|
||||
diagnostic.amend(Fix::replacement(
|
||||
unparse_stmt(
|
||||
&create_stmt(StmtKind::Return {
|
||||
@@ -267,7 +304,9 @@ pub fn return_bool_condition_directly(checker: &mut Checker, stmt: &Stmt) {
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
));
|
||||
} else if checker.is_builtin("bool") {
|
||||
} else {
|
||||
// Otherwise, we need to wrap the condition in a call to `bool`. (We've already
|
||||
// verified, above, that `bool` is a builtin.)
|
||||
diagnostic.amend(Fix::replacement(
|
||||
unparse_stmt(
|
||||
&create_stmt(StmtKind::Return {
|
||||
@@ -394,13 +433,15 @@ pub fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: Option<&
|
||||
return;
|
||||
}
|
||||
|
||||
let fixable = !has_comments(stmt, checker.locator);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UseTernaryOperator {
|
||||
contents: contents.clone(),
|
||||
fixable,
|
||||
},
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) && !has_comments(stmt, checker.locator) {
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(Fix::replacement(
|
||||
contents,
|
||||
stmt.location,
|
||||
@@ -525,13 +566,15 @@ pub fn use_dict_get_with_default(
|
||||
return;
|
||||
}
|
||||
|
||||
let fixable = !has_comments(stmt, checker.locator);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DictGetWithDefault {
|
||||
contents: contents.clone(),
|
||||
fixable,
|
||||
},
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) && !has_comments(stmt, checker.locator) {
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(Fix::replacement(
|
||||
contents,
|
||||
stmt.location,
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
use log::error;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Located, Stmt, StmtKind, Withitem};
|
||||
|
||||
use super::fix_with;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::helpers::{first_colon_range, has_comments_in};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
use super::fix_with;
|
||||
|
||||
define_violation!(
|
||||
pub struct MultipleWithStatements;
|
||||
pub struct MultipleWithStatements {
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for MultipleWithStatements {
|
||||
impl Violation for MultipleWithStatements {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
@@ -21,8 +27,13 @@ impl AlwaysAutofixableViolation for MultipleWithStatements {
|
||||
)
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
"Combine `with` statements".to_string()
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let MultipleWithStatements { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|_| format!("Combine `with` statements"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,35 +71,33 @@ pub fn multiple_with_statements(
|
||||
),
|
||||
checker.locator,
|
||||
);
|
||||
let fixable = !has_comments_in(
|
||||
Range::new(with_stmt.location, with_body[0].location),
|
||||
checker.locator,
|
||||
);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MultipleWithStatements,
|
||||
MultipleWithStatements { fixable },
|
||||
colon.map_or_else(
|
||||
|| Range::from_located(with_stmt),
|
||||
|colon| Range::new(with_stmt.location, colon.end_location),
|
||||
),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let nested_with = &with_body[0];
|
||||
if !has_comments_in(
|
||||
Range::new(with_stmt.location, nested_with.location),
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
match fix_with::fix_multiple_with_statements(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
with_stmt,
|
||||
) {
|
||||
match fix_with::fix_multiple_with_statements(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
with_stmt,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix
|
||||
.content
|
||||
.lines()
|
||||
.all(|line| line.len() <= checker.settings.line_length)
|
||||
{
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Ok(fix) => {
|
||||
if fix
|
||||
.content
|
||||
.lines()
|
||||
.all(|line| line.len() <= checker.settings.line_length)
|
||||
{
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(err) => error!("Failed to fix nested with: {err}"),
|
||||
}
|
||||
Err(err) => error!("Failed to fix nested with: {err}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -10,8 +10,7 @@ use crate::cst::matchers::{match_comparison, match_expression};
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::{Locator, Stylist};
|
||||
use crate::violation::{Availability, Violation};
|
||||
use crate::AutofixKind;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
define_violation!(
|
||||
pub struct YodaConditions {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
@@ -23,7 +24,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
@@ -44,7 +46,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
@@ -64,7 +67,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: false
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
@@ -74,7 +78,8 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 26
|
||||
column: 0
|
||||
@@ -95,7 +100,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 51
|
||||
column: 4
|
||||
@@ -125,7 +131,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 67
|
||||
column: 0
|
||||
@@ -155,7 +162,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 83
|
||||
column: 4
|
||||
@@ -177,7 +185,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 90
|
||||
column: 0
|
||||
@@ -199,7 +208,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements: ~
|
||||
NestedIfStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 117
|
||||
column: 4
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: a
|
||||
condition: a
|
||||
fixable: true
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
@@ -23,7 +24,8 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: a == b
|
||||
condition: a == b
|
||||
fixable: true
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
@@ -42,7 +44,8 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: b
|
||||
condition: b
|
||||
fixable: true
|
||||
location:
|
||||
row: 21
|
||||
column: 4
|
||||
@@ -61,7 +64,8 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: b
|
||||
condition: b
|
||||
fixable: true
|
||||
location:
|
||||
row: 32
|
||||
column: 8
|
||||
@@ -80,7 +84,8 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: a
|
||||
condition: a
|
||||
fixable: false
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
@@ -91,7 +96,8 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
cond: a
|
||||
condition: a
|
||||
fixable: false
|
||||
location:
|
||||
row: 83
|
||||
column: 4
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: b = c if a else d
|
||||
fixable: true
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
@@ -24,6 +25,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: abc = x if x > 0 else -x
|
||||
fixable: false
|
||||
location:
|
||||
row: 58
|
||||
column: 0
|
||||
@@ -35,6 +37,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: b = cccccccccccccccccccccccccccccccccccc if a else ddddddddddddddddddddddddddddddddddddd
|
||||
fixable: true
|
||||
location:
|
||||
row: 82
|
||||
column: 0
|
||||
@@ -54,6 +57,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: exitcode = 0 if True else 1
|
||||
fixable: false
|
||||
location:
|
||||
row: 97
|
||||
column: 0
|
||||
@@ -65,6 +69,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: x = 3 if True else 5
|
||||
fixable: false
|
||||
location:
|
||||
row: 104
|
||||
column: 0
|
||||
@@ -76,6 +81,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
UseTernaryOperator:
|
||||
contents: x = 3 if True else 5
|
||||
fixable: false
|
||||
location:
|
||||
row: 109
|
||||
column: 0
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
@@ -23,7 +24,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
@@ -44,7 +46,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: false
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
@@ -54,7 +57,8 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 19
|
||||
column: 0
|
||||
@@ -75,7 +79,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 53
|
||||
column: 4
|
||||
@@ -105,7 +110,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 68
|
||||
column: 0
|
||||
@@ -128,7 +134,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 76
|
||||
column: 0
|
||||
@@ -151,7 +158,8 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleWithStatements: ~
|
||||
MultipleWithStatements:
|
||||
fixable: true
|
||||
location:
|
||||
row: 84
|
||||
column: 0
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "var = a_dict.get(key, \"default1\")"
|
||||
fixable: true
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
@@ -24,6 +25,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "var = a_dict.get(key, \"default2\")"
|
||||
fixable: true
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
@@ -43,6 +45,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "var = a_dict.get(key, val1 + val2)"
|
||||
fixable: true
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
@@ -62,6 +65,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "var = a_dict.get(keys[idx], \"default\")"
|
||||
fixable: true
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
@@ -81,6 +85,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "var = dicts[idx].get(key, \"default\")"
|
||||
fixable: true
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
@@ -100,6 +105,7 @@ expression: diagnostics
|
||||
- kind:
|
||||
DictGetWithDefault:
|
||||
contents: "vars[idx] = a_dict.get(key, \"default\")"
|
||||
fixable: true
|
||||
location:
|
||||
row: 36
|
||||
column: 0
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::Stmt;
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::{Stmt, StmtKind};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use ruff_python::string::is_lower_with_underscore;
|
||||
|
||||
use crate::ast::helpers::{create_stmt, unparse_stmt};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
use crate::source_code::Stylist;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
pub type Settings = Strictness;
|
||||
|
||||
@@ -20,34 +28,145 @@ pub enum Strictness {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct RelativeImports(pub Strictness);
|
||||
/// ## What it does
|
||||
/// Checks for relative imports.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Absolute imports, or relative imports from siblings, are recommended by [PEP 8](https://peps.python.org/pep-0008/#imports):
|
||||
///
|
||||
/// > Absolute imports are recommended, as they are usually more readable and tend to be better behaved...
|
||||
/// > ```python
|
||||
/// > import mypkg.sibling
|
||||
/// > from mypkg import sibling
|
||||
/// > from mypkg.sibling import example
|
||||
/// > ```
|
||||
/// > However, explicit relative imports are an acceptable alternative to absolute imports,
|
||||
/// > especially when dealing with complex package layouts where using absolute imports would be
|
||||
/// > unnecessarily verbose:
|
||||
/// > ```python
|
||||
/// > from . import sibling
|
||||
/// > from .sibling import example
|
||||
/// > ```
|
||||
///
|
||||
/// Note that degree of strictness packages can be specified via the
|
||||
/// [`strictness`](https://github.com/charliermarsh/ruff#strictness)
|
||||
/// configuration option, which allows banning all relative imports (`strictness = "all"`)
|
||||
/// or only those that extend into the parent module or beyond (`strictness = "parents"`,
|
||||
/// the default).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from .. import foo
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from mypkg import foo
|
||||
/// ```
|
||||
pub struct RelativeImports {
|
||||
pub strictness: Strictness,
|
||||
}
|
||||
);
|
||||
impl Violation for RelativeImports {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let RelativeImports(strictness) = self;
|
||||
match strictness {
|
||||
match self.strictness {
|
||||
Strictness::Parents => format!("Relative imports from parent modules are banned"),
|
||||
Strictness::All => format!("Relative imports are banned"),
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
Some(|RelativeImports { strictness }| match strictness {
|
||||
Strictness::Parents => {
|
||||
format!("Replace relative imports from parent modules with absolute imports")
|
||||
}
|
||||
Strictness::All => format!("Replace relative imports with absolute imports"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_banned_relative_import(
|
||||
stmt: &Stmt,
|
||||
level: Option<&usize>,
|
||||
module: Option<&str>,
|
||||
path: &Path,
|
||||
stylist: &Stylist,
|
||||
) -> Option<Fix> {
|
||||
let base = if let Some(module) = module {
|
||||
module.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let mut parent = path.parent()?;
|
||||
for _ in 0..*level? {
|
||||
parent = parent.parent()?;
|
||||
}
|
||||
|
||||
let module_name = parent.file_name()?.to_string_lossy().to_string();
|
||||
|
||||
// Require import to be a valid PEP 8 module:
|
||||
// https://python.org/dev/peps/pep-0008/#package-and-module-names
|
||||
if !is_lower_with_underscore(module_name.as_str()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_import = if base.is_empty() {
|
||||
module_name
|
||||
} else {
|
||||
format!("{}.{}", module_name, base)
|
||||
};
|
||||
|
||||
let content = match &stmt.node {
|
||||
StmtKind::ImportFrom { names, .. } => unparse_stmt(
|
||||
&create_stmt(StmtKind::ImportFrom {
|
||||
module: Some(new_import),
|
||||
names: names.clone(),
|
||||
level: Some(0),
|
||||
}),
|
||||
stylist,
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Fix::replacement(
|
||||
content,
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// TID252
|
||||
pub fn banned_relative_import(
|
||||
checker: &Checker,
|
||||
stmt: &Stmt,
|
||||
level: Option<&usize>,
|
||||
module: Option<&str>,
|
||||
strictness: &Strictness,
|
||||
path: &Path,
|
||||
) -> Option<Diagnostic> {
|
||||
let strictness_level = match strictness {
|
||||
Strictness::All => 0,
|
||||
Strictness::Parents => 1,
|
||||
};
|
||||
if level? > &strictness_level {
|
||||
Some(Diagnostic::new(
|
||||
RelativeImports(strictness.clone()),
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RelativeImports {
|
||||
strictness: strictness.clone(),
|
||||
},
|
||||
Range::from_located(stmt),
|
||||
))
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) =
|
||||
fix_banned_relative_import(stmt, level, module, path, checker.stylist)
|
||||
{
|
||||
diagnostic.amend(fix);
|
||||
};
|
||||
}
|
||||
Some(diagnostic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -59,12 +178,13 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Strictness;
|
||||
use crate::assert_yaml_snapshot;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
use super::Strictness;
|
||||
|
||||
#[test]
|
||||
fn ban_parent_imports() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,65 +1,264 @@
|
||||
---
|
||||
source: src/rules/flake8_tidy_imports/relative_imports.rs
|
||||
source: crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 28
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 28
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 21
|
||||
fix:
|
||||
content:
|
||||
- from fixtures import sibling
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 21
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 28
|
||||
fix:
|
||||
content:
|
||||
- from fixtures.sibling import example
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 28
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix:
|
||||
content:
|
||||
- from test import parent
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 28
|
||||
fix:
|
||||
content:
|
||||
- from test.parent import example
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 28
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 27
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- from resources import grandparent
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 27
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: all
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 8
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 12
|
||||
column: 34
|
||||
fix:
|
||||
content:
|
||||
- from resources.grandparent import example
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 34
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 26
|
||||
fix:
|
||||
content:
|
||||
- from fixtures.parent import hello
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 26
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 19
|
||||
fix:
|
||||
content:
|
||||
- from fixtures.parent import hello_world
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 19
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 15
|
||||
fix:
|
||||
content:
|
||||
- from test.parent import world_hello
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 23
|
||||
column: 0
|
||||
end_location:
|
||||
row: 23
|
||||
column: 34
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 25
|
||||
column: 0
|
||||
end_location:
|
||||
row: 25
|
||||
column: 36
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 26
|
||||
column: 0
|
||||
end_location:
|
||||
row: 26
|
||||
column: 38
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 56
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 28
|
||||
column: 0
|
||||
end_location:
|
||||
row: 28
|
||||
column: 40
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 29
|
||||
column: 0
|
||||
end_location:
|
||||
row: 29
|
||||
column: 44
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: all
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
end_location:
|
||||
row: 30
|
||||
column: 62
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,45 +1,188 @@
|
||||
---
|
||||
source: src/rules/flake8_tidy_imports/relative_imports.rs
|
||||
source: crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
RelativeImports: parents
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 4
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- from test import parent
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: parents
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 5
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
row: 10
|
||||
column: 28
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- from test.parent import example
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 28
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: parents
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 7
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
row: 11
|
||||
column: 27
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- from resources import grandparent
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 27
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports: parents
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 8
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 12
|
||||
column: 34
|
||||
fix:
|
||||
content:
|
||||
- from resources.grandparent import example
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 34
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 15
|
||||
fix:
|
||||
content:
|
||||
- from test.parent import world_hello
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 23
|
||||
column: 0
|
||||
end_location:
|
||||
row: 23
|
||||
column: 34
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 25
|
||||
column: 0
|
||||
end_location:
|
||||
row: 25
|
||||
column: 36
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 26
|
||||
column: 0
|
||||
end_location:
|
||||
row: 26
|
||||
column: 38
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 56
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 28
|
||||
column: 0
|
||||
end_location:
|
||||
row: 28
|
||||
column: 40
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 29
|
||||
column: 0
|
||||
end_location:
|
||||
row: 29
|
||||
column: 44
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RelativeImports:
|
||||
strictness: parents
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
end_location:
|
||||
row: 30
|
||||
column: 62
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ mod tests {
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_8.py"); "TCH004_8")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_9.py"); "TCH004_9")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"); "TCH004_10")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"); "TCH004_11")]
|
||||
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"); "TCH005")]
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"); "strict")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -164,6 +164,7 @@ pub fn typing_only_runtime_import(
|
||||
package,
|
||||
&settings.isort.known_first_party,
|
||||
&settings.isort.known_third_party,
|
||||
&settings.isort.known_local_folder,
|
||||
&settings.isort.extra_standard_library,
|
||||
settings.target_version,
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
RuntimeImportInTypeCheckingBlock:
|
||||
full_name: typing.List
|
||||
location:
|
||||
row: 4
|
||||
column: 23
|
||||
end_location:
|
||||
row: 4
|
||||
column: 27
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::rules::flake8_use_pathlib::violations::{
|
||||
PathlibRemove, PathlibRename, PathlibReplace, PathlibRmdir, PathlibSamefile, PathlibSplitext,
|
||||
PathlibStat, PathlibUnlink,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
|
||||
if let Some(diagnostic_kind) =
|
||||
@@ -32,7 +33,6 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
|
||||
["os", "path", "isdir"] => Some(PathlibIsDir.into()),
|
||||
["os", "path", "isfile"] => Some(PathlibIsFile.into()),
|
||||
["os", "path", "islink"] => Some(PathlibIsLink.into()),
|
||||
["os", "readlink"] => Some(PathlibReadlink.into()),
|
||||
["os", "stat"] => Some(PathlibStat.into()),
|
||||
["os", "path", "isabs"] => Some(PathlibIsAbs.into()),
|
||||
["os", "path", "join"] => Some(PathlibJoin.into()),
|
||||
@@ -42,6 +42,10 @@ pub fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
|
||||
["os", "path", "splitext"] => Some(PathlibSplitext.into()),
|
||||
["", "open"] => Some(PathlibOpen.into()),
|
||||
["py", "path", "local"] => Some(PathlibPyPath.into()),
|
||||
// Python 3.9+
|
||||
["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => {
|
||||
Some(PathlibReadlink.into())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ enum Reason<'a> {
|
||||
NonZeroLevel,
|
||||
KnownFirstParty,
|
||||
KnownThirdParty,
|
||||
KnownLocalFolder,
|
||||
ExtraStandardLibrary,
|
||||
Future,
|
||||
KnownStandardLibrary,
|
||||
@@ -43,6 +44,7 @@ pub fn categorize(
|
||||
package: Option<&Path>,
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
known_local_folder: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
target_version: PythonVersion,
|
||||
) -> ImportType {
|
||||
@@ -53,6 +55,8 @@ pub fn categorize(
|
||||
(ImportType::FirstParty, Reason::KnownFirstParty)
|
||||
} else if known_third_party.contains(module_base) {
|
||||
(ImportType::ThirdParty, Reason::KnownThirdParty)
|
||||
} else if known_local_folder.contains(module_base) {
|
||||
(ImportType::LocalFolder, Reason::KnownLocalFolder)
|
||||
} else if extra_standard_library.contains(module_base) {
|
||||
(ImportType::StandardLibrary, Reason::ExtraStandardLibrary)
|
||||
} else if module_base == "__future__" {
|
||||
@@ -98,12 +102,14 @@ fn match_sources<'a>(paths: &'a [PathBuf], base: &str) -> Option<&'a Path> {
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn categorize_imports<'a>(
|
||||
block: ImportBlock<'a>,
|
||||
src: &[PathBuf],
|
||||
package: Option<&Path>,
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
known_local_folder: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
target_version: PythonVersion,
|
||||
) -> BTreeMap<ImportType, ImportBlock<'a>> {
|
||||
@@ -117,6 +123,7 @@ pub fn categorize_imports<'a>(
|
||||
package,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
extra_standard_library,
|
||||
target_version,
|
||||
);
|
||||
@@ -135,6 +142,7 @@ pub fn categorize_imports<'a>(
|
||||
package,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
extra_standard_library,
|
||||
target_version,
|
||||
);
|
||||
@@ -153,6 +161,7 @@ pub fn categorize_imports<'a>(
|
||||
package,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
extra_standard_library,
|
||||
target_version,
|
||||
);
|
||||
@@ -171,6 +180,7 @@ pub fn categorize_imports<'a>(
|
||||
package,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
extra_standard_library,
|
||||
target_version,
|
||||
);
|
||||
|
||||
@@ -122,6 +122,7 @@ pub fn format_imports(
|
||||
force_wrap_aliases: bool,
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
known_local_folder: &BTreeSet<String>,
|
||||
order_by_type: bool,
|
||||
relative_imports_order: RelativeImportsOrder,
|
||||
single_line_exclusions: &BTreeSet<String>,
|
||||
@@ -155,6 +156,7 @@ pub fn format_imports(
|
||||
force_wrap_aliases,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
order_by_type,
|
||||
relative_imports_order,
|
||||
single_line_exclusions,
|
||||
@@ -212,6 +214,7 @@ fn format_import_block(
|
||||
force_wrap_aliases: bool,
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
known_local_folder: &BTreeSet<String>,
|
||||
order_by_type: bool,
|
||||
relative_imports_order: RelativeImportsOrder,
|
||||
single_line_exclusions: &BTreeSet<String>,
|
||||
@@ -229,6 +232,7 @@ fn format_import_block(
|
||||
package,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
known_local_folder,
|
||||
extra_standard_library,
|
||||
target_version,
|
||||
);
|
||||
@@ -366,6 +370,12 @@ mod tests {
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&Settings {
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
isort: super::settings::Settings {
|
||||
known_local_folder: vec!["ruff".to_string()]
|
||||
.into_iter()
|
||||
.collect::<BTreeSet<_>>(),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -15,6 +15,26 @@ use crate::source_code::{Locator, Stylist};
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Adds any required imports, as specified by the user, to the top of the file.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In some projects, certain imports are required to be present in all files. For
|
||||
/// example, some projects assume that `from __future__ import annotations` is enabled,
|
||||
/// and thus require that import to be present in all files. Omitting a "required" import
|
||||
/// (as specified by the user) can cause errors or unexpected behavior.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import typing
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// import typing
|
||||
/// ```
|
||||
pub struct MissingRequiredImport(pub String);
|
||||
);
|
||||
impl AlwaysAutofixableViolation for MissingRequiredImport {
|
||||
|
||||
@@ -19,6 +19,24 @@ use crate::source_code::{Indexer, Locator, Stylist};
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// De-duplicates, groups, and sorts imports based on the provided `isort` settings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use a common convention for imports to make your code
|
||||
/// more readable and idiomatic.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pandas
|
||||
/// import numpy as np
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import numpy as np
|
||||
/// import pandas
|
||||
/// ```
|
||||
pub struct UnsortedImports;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for UnsortedImports {
|
||||
@@ -109,6 +127,7 @@ pub fn organize_imports(
|
||||
settings.isort.force_wrap_aliases,
|
||||
&settings.isort.known_first_party,
|
||||
&settings.isort.known_third_party,
|
||||
&settings.isort.known_local_folder,
|
||||
settings.isort.order_by_type,
|
||||
settings.isort.relative_imports_order,
|
||||
&settings.isort.single_line_exclusions,
|
||||
|
||||
@@ -47,7 +47,7 @@ pub struct Options {
|
||||
/// exactly one member. For example, this formatting would be retained,
|
||||
/// rather than condensing to a single line:
|
||||
///
|
||||
/// ```py
|
||||
/// ```python
|
||||
/// from .utils import (
|
||||
/// test_directory as test_directory,
|
||||
/// test_id as test_id
|
||||
@@ -138,6 +138,17 @@ pub struct Options {
|
||||
/// A list of modules to consider third-party, regardless of whether they
|
||||
/// can be identified as such via introspection of the local filesystem.
|
||||
pub known_third_party: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
known-local-folder = ["src"]
|
||||
"#
|
||||
)]
|
||||
/// A list of modules to consider being a local folder.
|
||||
/// Generally, this is reserved for relative
|
||||
/// imports (from . import module).
|
||||
pub known_local_folder: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
@@ -247,6 +258,7 @@ pub struct Settings {
|
||||
pub force_wrap_aliases: bool,
|
||||
pub known_first_party: BTreeSet<String>,
|
||||
pub known_third_party: BTreeSet<String>,
|
||||
pub known_local_folder: BTreeSet<String>,
|
||||
pub order_by_type: bool,
|
||||
pub relative_imports_order: RelativeImportsOrder,
|
||||
pub single_line_exclusions: BTreeSet<String>,
|
||||
@@ -270,6 +282,7 @@ impl Default for Settings {
|
||||
force_wrap_aliases: false,
|
||||
known_first_party: BTreeSet::new(),
|
||||
known_third_party: BTreeSet::new(),
|
||||
known_local_folder: BTreeSet::new(),
|
||||
order_by_type: true,
|
||||
relative_imports_order: RelativeImportsOrder::default(),
|
||||
single_line_exclusions: BTreeSet::new(),
|
||||
@@ -297,6 +310,7 @@ impl From<Options> for Settings {
|
||||
force_wrap_aliases: options.force_wrap_aliases.unwrap_or(false),
|
||||
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
|
||||
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
|
||||
known_local_folder: BTreeSet::from_iter(options.known_local_folder.unwrap_or_default()),
|
||||
order_by_type: options.order_by_type.unwrap_or(true),
|
||||
relative_imports_order: options.relative_imports_order.unwrap_or_default(),
|
||||
single_line_exclusions: BTreeSet::from_iter(
|
||||
@@ -324,6 +338,7 @@ impl From<Settings> for Options {
|
||||
force_wrap_aliases: Some(settings.force_wrap_aliases),
|
||||
known_first_party: Some(settings.known_first_party.into_iter().collect()),
|
||||
known_third_party: Some(settings.known_third_party.into_iter().collect()),
|
||||
known_local_folder: Some(settings.known_local_folder.into_iter().collect()),
|
||||
order_by_type: Some(settings.order_by_type),
|
||||
relative_imports_order: Some(settings.relative_imports_order),
|
||||
single_line_exclusions: Some(settings.single_line_exclusions.into_iter().collect()),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user