Compare commits

...

113 Commits

Author SHA1 Message Date
Evan Rittenhouse
889c05c87e Explicitly put Path(...) in Pathlib violations (#3333) 2023-03-04 04:33:12 +00:00
Charlie Marsh
bbbc44336e Bump version to 0.0.254 (#3331) 2023-03-03 19:11:07 -05:00
Charlie Marsh
d216b2aaa8 Treat callables within type definitions as default-non-types (#3329) 2023-03-03 23:07:30 +00:00
Charlie Marsh
367cc43c42 Un-gate PEP 604 isinstance rewrites from keep_runtime_typing checks (#3328) 2023-03-03 17:29:41 -05:00
Charlie Marsh
b5b26d5a3e Gate PEP604 isinstance rewrites behind Python 3.10+ (#3327) 2023-03-03 22:22:14 +00:00
Charlie Marsh
dedf8aa5cc Use presence of convention-specific sections during docstring inference (#3325) 2023-03-03 17:13:11 -05:00
Charlie Marsh
eb42ce9319 Extend RET503 autofixes to "end of statement", including comments (#3324) 2023-03-03 19:15:55 +00:00
Micha Reiser
cdbe2ee496 refactor: Introduce CacheKey trait (#3323)
This PR introduces a new `CacheKey` trait for types that can be used as a cache key.

I'm not entirely sure if this is worth the "overhead", but I was surprised to find `HashableHashSet` and got scared when I looked at the time complexity of the `hash` function. These implementations must be extremely slow in hashed collections.

I then searched for usages and quickly realized that only the cache uses these `Hash` implementations, where performance is less sensitive.

This PR introduces a new `CacheKey` trait to communicate the difference between a hash and computing a key for the cache. The new trait can be implemented for types that don't implement `Hash` for performance reasons, and we can define additional constraints on the implementation:  For example, we'll want to enforce portability when we add remote caching support. Using a different trait further allows us not to implement it for types without stable identities (e.g. pointers) or use other implementations than the standard hash function.
2023-03-03 18:29:49 +00:00
konstin
d1288dc2b1 Remove maturin build from CI (#3322)
maturin build generally works when cargo build works, so imho it's not worth running it with every CI run.
2023-03-03 16:50:26 +01:00
konstin
3bcffb5bdd Add flake-pyi PYI033 "Do not use type comments in stubs" (#3302) 2023-03-03 10:45:34 -05:00
Martin Packman
98209be8aa Detect quote style ignoring docstrings (#3306)
Currently the quote style of the first string in a file is used for autodetecting what to use when rewriting code for fixes. This is an okay heuristic, but often the first line in a file is a docstring, rather than a string constant, and it's not uncommon for pre-Black code to have different quoting styles for those.

For example, in the Google style guide:
https://google.github.io/styleguide/pyguide.html
> Be consistent with your choice of string quote character within a file. Pick ' or " and stick with it. ... Docstrings must use """ regardless.

This branch adjusts the logic to instead skip over any `"""` triple doublequote string tokens. The default, if there are no single quoted strings, is still to use double quote as the style.
2023-03-02 23:59:33 -05:00
Charlie Marsh
a03fa93c3a Abort when unable to fix relative imports past module root (#3319) 2023-03-03 04:38:51 +00:00
Charlie Marsh
4de3882088 Upgrade RustPython (#3316) 2023-03-02 22:59:29 -05:00
Charlie Marsh
3a98b68dc0 Always include @classmethod and @staticmethod in decorator lists (#3314) 2023-03-03 03:49:04 +00:00
Carlos Gonçalves
7e291e542d feat(e225,226,227,228): add rules (#3300) 2023-03-02 22:54:45 +00:00
Carlos Gonçalves
6f649d6579 feat(E211): add rule + autofix (#3313) 2023-03-02 22:48:04 +00:00
Yaroslav Chvanov
508bc605a5 Implement property-decorators configuration option for pydocstyle (#3311) 2023-03-02 16:59:56 -05:00
Charlie Marsh
ffdf6e35e6 Treat function type annotations within classes as runtime-required (#3312) 2023-03-02 16:45:26 -05:00
Martin Lehoux
886992c6c2 Replace tuples with type union in isinstance or issubclass calls (#3280) 2023-03-02 15:59:15 -05:00
Charlie Marsh
187104e396 Flag out-of-date docs on CI (#3309) 2023-03-02 15:55:39 -05:00
Charlie Marsh
3ed539d50e Add a CLI flag to force-ignore noqa directives (#3296) 2023-03-01 22:28:13 -05:00
Charlie Marsh
4a70a4c323 Ignore unused imports in ModuleNotFoundError blocks (#3288) 2023-03-01 18:08:37 -05:00
Charlie Marsh
310f13c7db Redirect RUF004 to B026 (#3293) 2023-03-01 13:00:02 -05:00
konstin
2168404fc2 flake8-pyi PYI006 bad version info comparison (#3291)
Implement PYI006 "bad version info comparison"

## What it does

Ensures that you only `<` and `>=` for version info comparisons with
`sys.version_info` in `.pyi` files. All other comparisons such as
`<`, `<=` and `==` are banned.

## Why is this bad?

```python
>>> import sys
>>> print(sys.version_info)
sys.version_info(major=3, minor=8, micro=10, releaselevel='final', serial=0)
>>> print(sys.version_info > (3, 8))
True
>>> print(sys.version_info == (3, 8))
False
>>> print(sys.version_info <= (3, 8))
False
>>> print(sys.version_info in (3, 8))
False
```

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-03-01 18:58:57 +01:00
Charlie Marsh
a032b66c2e Avoid PEP 585 rewrites when builtins are shadowed (#3286) 2023-02-28 23:25:42 +00:00
Charlie Marsh
af5f7dbd83 Avoid pluralization for single --add-noqa result (#3282) 2023-02-28 15:41:18 -05:00
Charlie Marsh
8066607ea3 Add a preliminary tutorial (#3281) 2023-02-28 20:31:27 +00:00
Andy Freeland
0ed9fccce9 Upgrade RustPython (#3277)
Fixes #3207.
2023-02-28 12:21:28 -05:00
Carlos Gonçalves
074a343a63 feat(E251,E252): add rules (#3274) 2023-02-28 12:02:36 -05:00
Charlie Marsh
c7e09b54b0 Use expression span for yoda-conditions fixes (#3276) 2023-02-28 16:59:02 +00:00
Charlie Marsh
67d1f74587 Avoid raising TRY200 violations within new scopes (#3275) 2023-02-28 11:56:29 -05:00
Matthew Lloyd
1c79dff3bd Improve the message for PLW2901: use "outer" and "inner" judiciously (#3263) 2023-02-28 16:33:01 +00:00
Charlie Marsh
f5f09b489b Introduce dedicated CST tokens for other operator kinds (#3267) 2023-02-27 23:54:57 -05:00
Charlie Marsh
061495a9eb Make BoolOp its own located token (#3265) 2023-02-28 03:43:28 +00:00
Charlie Marsh
470e1c1754 Preserve comments on non-defaulted arguments (#3264) 2023-02-27 23:41:40 +00:00
Charlie Marsh
16be691712 Enable more non-panicking formatter tests (#3262) 2023-02-27 18:21:53 -05:00
Charlie Marsh
270015865b Don't flag keyword-based logging format strings (#3261) 2023-02-27 23:11:13 +00:00
Charlie Marsh
ccfa9d5b20 Deduplicate SIM116 errors (#3260) 2023-02-27 18:08:45 -05:00
Charlie Marsh
2261e194a0 Create dedicated Body nodes in the formatter CST (#3223) 2023-02-27 22:55:05 +00:00
Ville Skyttä
cd6413ca09 Match non-lowercase with S105 again (#3258) 2023-02-27 16:38:23 -05:00
Charlie Marsh
c65585e14a Use identifier_range for a few more rules (#3254) 2023-02-27 18:23:33 +00:00
Charlie Marsh
d2a6ed7be6 Upgrade RustPython (#3252) 2023-02-27 18:21:06 +00:00
Charlie Marsh
16e2dae0c2 Handle empty NamedTuple and TypedDict conversions (#3251) 2023-02-27 11:18:34 -05:00
Carlos Gonçalves
e8ba9c9e21 feat(W191): add indentation_contains_tabs (#3249) 2023-02-27 10:36:03 -05:00
Jonathan Plasse
d285f5c90a Run automatically format code blocks with Black (#3191) 2023-02-27 10:14:05 -05:00
Charlie Marsh
386ca7c9a1 Bump version to 0.0.253 (#3245) 2023-02-26 23:10:04 -05:00
Charlie Marsh
40c5abf16e Avoid flagging Pylint logging rules with starred arguments (#3244) 2023-02-26 22:58:24 -05:00
Matthew Lloyd
7e7aec7d74 Expand the range of the COM812 autofix to include the preceding token (#3241)
This prevents the UP034 autofix simultaneously stripping the
parentheses from generators in the same linter pass, which causes
a SyntaxError.

Closes #3234.

With this fix:

```python
$ cat test.py
the_first_one = next(
    (i for i in range(10) if i // 2 == 0)
)

$ cargo run --bin ruff check test.py --no-cache --select UP034,COM812 --fix
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/ruff check test.py --no-cache --select UP034,COM812 --fix`
Found 1 error (1 fixed, 0 remaining).

$ cat test.py
the_first_one = next(
    i for i in range(10) if i // 2 == 0
)
```

* Use format

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-02-27 03:47:07 +00:00
Ivan Gozali
4b5538f74e [pylint] W0603: global-statement (#3227)
Implements pylint rule [W0603: global-statement](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/global-statement.html).

Currently checks for global statement usage in a few StmtKinds (as tested in the `pylint` `global-statement` test case [here](b70d2abd7f/tests/functional/g/globals.py)):

* Assign
* AugAssign
* ClassDef
* FunctionDef | AsyncFunctionDef
* Import
* ImportFrom
* Delete
2023-02-26 23:40:24 +00:00
Charlie Marsh
36d134fd41 Bump lint rule count to 500+ (#3240) 2023-02-26 18:10:09 -05:00
Chris Chan
0b7d6b9097 Implement pylint's else-if-used rule (PLR5501) (#3231)
Attempt to implement else-if-used
https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/else-if-used.html

Issue #970
2023-02-26 22:42:33 +00:00
Nick Pope
994e2e0903 Rename some flake8-simplify rules (#2915)
Renames the following rules that stood out to me at a glance as needing better names:

- `or-true` to `expr-or-true`
- `and-false` to `expr-and-false`
- `a-or-not-a` to `expr-or-not-expr`
- `a-and-not-a` to `expr-and-not-expr`

Related to #2902.
2023-02-26 22:35:15 +00:00
Luc Khai Hai
bc79f540e4 [flake8-django] DJ003, DJ006, DJ007 (#3236)
Implements [flake8-django](https://github.com/rocioar/flake8-django) rules:
- DJ03
- DJ06
- DJ07
2023-02-26 22:29:42 +00:00
Steve Dignam
3a78b59314 [flake8-pyi]: PYI011, PYI014 (#3238)
Implement PYI011 and PYI014 with the latest changes:

https://github.com/PyCQA/flake8-pyi/pull/326
https://github.com/PyCQA/flake8-pyi/issues/316

rel: https://github.com/charliermarsh/ruff/issues/848
rel: 4212bec43d/pyi.py (L718)
2023-02-26 22:11:58 +00:00
Edgar R. M
5f83851329 [pydocstyle]: Implement ignore-decorators (#3229) 2023-02-26 21:40:01 +00:00
Carlos Gonçalves
484ce7b8fc feat(E275): add Missing whitespace after keyword (#3225) 2023-02-26 21:36:05 +00:00
Charlie Marsh
1c75071136 Implement basic rendering of remaining AST nodes (#3233) 2023-02-26 05:05:56 +00:00
Charlie Marsh
51bca19c1d Add builders for common comment rendering (#3232) 2023-02-26 04:16:24 +00:00
Steve Dignam
a8a312e862 [flake8-pyi]: PYI009, PYI010, PYI021 (#3230)
PYI009 and PYI010 are very similar, always use `...` in function and class bodies in stubs.

PYI021 bans doc strings in stubs.

I think all of these rules should be relatively straightforward to implement auto fixes for but can do that later once we get all the other rules added.

rel: https://github.com/charliermarsh/ruff/issues/848
2023-02-25 22:29:04 -05:00
Ran Benita
33c31cda27 Add noqa_row to diagnostics JSON format (#3228)
In ruff-lsp (https://github.com/charliermarsh/ruff-lsp/pull/76) we want to add a "Disable \<rule\> for this line" quickfix. However, finding the correct line into which the `noqa` comment should be inserted is non-trivial (multi-line strings for example).

Ruff already has this info, so expose it in the JSON output for use by ruff-lsp.
2023-02-25 18:13:16 -05:00
Edgar R. M
cd9fbeb560 [bandit]: Do not treat "passed" as "password" for S105/S106/S107 (#3222) 2023-02-25 15:32:53 -05:00
Jeong YunWon
84e96cdcd9 More enum work (#3212) 2023-02-25 11:40:16 -05:00
Charlie Marsh
248590224a Avoid flagging logging-too-few-args with no arguments (#3220) 2023-02-24 21:57:49 -05:00
Charlie Marsh
bbc55cdb04 Allow ruff.toml file to be dot-prefixed (as .ruff.toml) (#3221) 2023-02-24 23:14:26 +00:00
Charlie Marsh
2792439eac Add TextMate to editor-integrations.md (#3219) 2023-02-24 22:57:33 +00:00
Charlie Marsh
0694aee1b6 Avoid EXE001 and EXE002 errors from stdin input (#3218) 2023-02-24 22:55:32 +00:00
Charlie Marsh
a17b5c134a Avoid rewriting any PEP 604 runtime annotations (#3217)
Following `pyupgrade`, we'll just _never_ touch these.

Closes #2981.

Closes #3215.
2023-02-24 22:39:58 +00:00
Charlie Marsh
42f61535b5 Normalize treatment of aliased and unaliased imports (#3216) 2023-02-24 22:11:03 +00:00
Carlos Gonçalves
1c01b3c934 fix(docs): broken links inside Configuration.md (#3205) 2023-02-24 18:55:33 +00:00
Jonathan Plasse
39b9a1637f Fix Markdown errors in docs (#3187) 2023-02-24 13:06:48 -05:00
Jonathan Plasse
2c692e3acf Remove --all from cargo fmt|test (#3208) 2023-02-24 12:52:59 -05:00
monosans
24add5f56c Avoid boolean-trap rules for ConfigParser get() methods (#3209) 2023-02-24 12:52:33 -05:00
Samuel Cormier-Iijima
0b7736ad79 [flake8-tidy-imports] fix autofix for relative imports (#3197) 2023-02-23 23:40:28 -05:00
Charlie Marsh
eef85067c8 Exclude globsets for --show-settings (#3201) 2023-02-24 04:23:00 +00:00
Jeong YunWon
da98fab4ae Adapt is-macro for a few enums (#3182) 2023-02-24 04:06:56 +00:00
Charlie Marsh
0f37a98d91 Use then-some pattern for sometimes-fixable rules (#3199) 2023-02-24 03:57:14 +00:00
Charlie Marsh
f38624824d Avoid autofixing some PT violations when comments are present (#3198) 2023-02-24 03:48:41 +00:00
Charlie Marsh
159422071e Handle end-of-line comments on excepthandler and alias (#3196) 2023-02-23 22:35:39 -05:00
Charlie Marsh
6eaacf96be Introduce a new CST element for slice segments (#3195) 2023-02-24 00:49:41 +00:00
Charlie Marsh
eb15371453 Make Locator available in AST-to-CST conversion pass (#3194) 2023-02-23 19:43:03 -05:00
Matt Nawara
198b301baf [pycodestyle] trailing-whitespace, blank-line-contains-whitespace (W291, W293) (#3122) 2023-02-23 19:04:45 -05:00
Jeong YunWon
c8c575dd43 Adapt BoolLike to flags (#3175) 2023-02-23 16:31:46 -05:00
Jonathan Plasse
6e54cd8233 Normalize relative markdown links (#3190) 2023-02-23 16:24:31 -05:00
Jonathan Plasse
a688a237d7 Add black to pre-commit (#3192) 2023-02-23 16:24:23 -05:00
Charlie Marsh
bda2a0007a Parenthesize numbers during attribute accesses (#3189) 2023-02-23 14:57:23 -05:00
Charlie Marsh
32d165b7ad Implement complex literal formatting (#3186) 2023-02-23 19:09:33 +00:00
Charlie Marsh
ac79bf4ee9 Implement float literal formatting (#3184) 2023-02-23 14:02:23 -05:00
Charlie Marsh
376eab3a53 Implement integer literal formatting (#3183) 2023-02-23 18:31:56 +00:00
Charlie Marsh
08be7bd285 Add a TODO to string_literal (#3181) 2023-02-23 12:46:20 -05:00
Charlie Marsh
f5241451d8 Use writeln with --show-settings (#3180) 2023-02-23 17:23:31 +00:00
Charlie Marsh
c9fe0708cb Run cargo update (#3179) 2023-02-23 12:09:36 -05:00
Charlie Marsh
09f8c487ea Update RustPython to support *tuple annotations (#3178) 2023-02-23 16:58:38 +00:00
Charlie Marsh
1e7233a8eb Add support for reformatting byte strings (#3176) 2023-02-23 16:50:24 +00:00
Charlie Marsh
f967f344fc Add support for basic Constant::Str formatting (#3173)
This PR enables us to apply the proper quotation marks, including support for escapes. There are some significant TODOs, especially around implicit concatenations like:

```py
(
  "abc"
  "def"
)
```

Which are represented as a single AST node, which requires us to tokenize _within_ the formatter to identify all the individual string parts.
2023-02-23 16:23:10 +00:00
Charlie Marsh
095f005bf4 Move RustPython vendored and helper code into its own crate (#3171) 2023-02-23 14:14:16 +00:00
Charlie Marsh
0f04aa2a5f Bind star patterns in match statements (#3169) 2023-02-23 12:39:03 +00:00
Jonathan Plasse
ad7ba77fff Fix ExceptionGroup F821 false positive (#3167) 2023-02-23 12:36:11 +00:00
Jeong YunWon
77d43795f8 Replace Autofix::is_enabled to result_like::BoolLike (#3165) 2023-02-23 07:29:13 -05:00
Jeong YunWon
4357f2be0f Add Autofix::is_enabled() to remove repeative patterns (#3159) 2023-02-22 23:52:07 -05:00
Charlie Marsh
e5c1f95545 Check-in updated snapshot (#3161) 2023-02-23 03:42:27 +00:00
Charlie Marsh
227ff62a4e Don't touch tuple brackets after in (#3160) 2023-02-23 03:10:24 +00:00
Charlie Marsh
d8e4902516 Un-modify tupleassign and function2 tests (#3158)
I manually changed these in #3080 and #3083 to get the tests passing (with notes around the deviations) -- but that's no longer necessary, now that we have proper testing that takes deviations into account.
2023-02-23 02:37:25 +00:00
Matthew Lloyd
e66739884f Add note about prioritizing naming convention over preservation (#3157) 2023-02-23 02:32:46 +00:00
Charlie Marsh
5fd827545b Add a trailing newline to all .py.expect files (#3156)
This just re-formats all the `.py.expect` files with Black, both to add a trailing newline and be doubly-certain that they're correctly formatted.

I also ensured that we add a hard line break after each statement, and that we avoid including an extra newline in the generated Markdown (since the code should contain the exact expected newlines).
2023-02-23 02:29:27 +00:00
Matthew Lloyd
c1ddcb8a60 [flake8-pie] Unnecessary list comprehension, with autofix (PIE802) (#3149) 2023-02-22 20:58:45 -05:00
Charlie Marsh
48a317d5f6 Change via to using (#3155) 2023-02-23 01:47:15 +00:00
Charlie Marsh
74e18b6cff Split up some docs sections (#3154) 2023-02-22 20:18:10 -05:00
Charlie Marsh
21d02cd51f Omit non-.py[i] files from module naming rules (#3153) 2023-02-23 00:38:46 +00:00
Charlie Marsh
049e77b939 Follow-up with some small doc changes (#3152) 2023-02-23 00:35:22 +00:00
Charlie Marsh
b9bfb81e36 Move configuration out of README and into permanent docs (#3150) 2023-02-22 19:25:53 -05:00
Charlie Marsh
2d4fae45d9 Avoid flagging unfixable TypedDict and NamedTuple definitions (#3148) 2023-02-22 23:23:25 +00:00
Charlie Marsh
726adb7efc Avoid suggesting 'is' for constant literals (#3146) 2023-02-22 22:37:22 +00:00
Charlie Marsh
dbdfdeb0e1 Add pre-commit note to docs (#3145) 2023-02-22 17:22:47 -05:00
546 changed files with 17696 additions and 7454 deletions

View File

@@ -29,7 +29,7 @@ jobs:
- run: ./target/debug/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json
- run: git diff --exit-code -- README.md ruff.schema.json docs
cargo-fmt:
name: "cargo fmt"
@@ -109,21 +109,6 @@ jobs:
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
maturin-build:
name: "maturin build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- run: pip install maturin
- run: maturin build -b bin
- run: python scripts/transform_readme.py --target pypi
typos:
name: "spell check"
runs-on: ubuntu-latest

View File

@@ -5,6 +5,14 @@ repos:
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
- mdformat-black
- black==23.1.0 # Must be the latest version of Black
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
hooks:
@@ -13,7 +21,6 @@ repos:
- --disable
- MD013 # line-length
- MD033 # no-inline-html
- MD041 # first-line-h1
- --
- repo: local
@@ -46,5 +53,16 @@ repos:
pass_filenames: false
exclude: target
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
ci:
skip: [cargo-fmt, clippy, dev-generate-all]

View File

@@ -20,8 +20,7 @@ the intention of adding a stable public API in the future.
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
Previously, the interplay between `select` and its related options could lead to unexpected
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore
ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
examples.)
When Ruff determines the enabled rule set, it has to reconcile `select` and `ignore` from a variety
@@ -74,7 +73,7 @@ ruff rule E402 --format json # Works! (And preferred.)
This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
* Subcommands will now fail when invoked with unsupported arguments, instead
- Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
```console
@@ -83,16 +82,16 @@ no change in behavior. However, please note the following exceptions:
(the `clean` command doesn't support `--respect-gitignore`.)
* The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
- The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
* Scripts that invoke ruff should supply `--` before any positional arguments.
- Scripts that invoke ruff should supply `--` before any positional arguments.
(The semantics of `ruff -- <arg>` have not changed.)
* `--explain` previously treated `--format grouped` as a synonym for `--format text`.
- `--explain` previously treated `--format grouped` as a synonym for `--format text`.
This is no longer supported; instead, use `--format text`.
## 0.0.226

View File

@@ -1,16 +1,16 @@
# Contributor Covenant Code of Conduct
* [Our Pledge](#our-pledge)
* [Our Standards](#our-standards)
* [Enforcement Responsibilities](#enforcement-responsibilities)
* [Scope](#scope)
* [Enforcement](#enforcement)
* [Enforcement Guidelines](#enforcement-guidelines)
* [1. Correction](#1-correction)
* [2. Warning](#2-warning)
* [3. Temporary Ban](#3-temporary-ban)
* [4. Permanent Ban](#4-permanent-ban)
* [Attribution](#attribution)
- [Our Pledge](#our-pledge)
- [Our Standards](#our-standards)
- [Enforcement Responsibilities](#enforcement-responsibilities)
- [Scope](#scope)
- [Enforcement](#enforcement)
- [Enforcement Guidelines](#enforcement-guidelines)
- [1. Correction](#1-correction)
- [2. Warning](#2-warning)
- [3. Temporary Ban](#3-temporary-ban)
- [4. Permanent Ban](#4-permanent-ban)
- [Attribution](#attribution)
## Our Pledge
@@ -29,23 +29,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -132,7 +132,7 @@ version 2.0, available [here](https://www.contributor-covenant.org/version/2/0/c
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq).
Translations are available [here](https://www.contributor-covenant.org/translations).
[homepage]: https://www.contributor-covenant.org

View File

@@ -2,16 +2,16 @@
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
* [The Basics](#the-basics)
* [Prerequisites](#prerequisites)
* [Development](#development)
* [Project Structure](#project-structure)
* [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
* [Rule naming convention](#rule-naming-convention)
* [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
* [MkDocs](#mkdocs)
* [Release Process](#release-process)
* [Benchmarks](#benchmarks)
- [The Basics](#the-basics)
- [Prerequisites](#prerequisites)
- [Development](#development)
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
- [Benchmarks](#benchmarks)
## The Basics
@@ -56,9 +56,9 @@ Prior to opening a pull request, ensure that your code has been auto-formatted,
and that it passes both the lint and test validation checks:
```shell
cargo fmt --all # Auto-formatting...
cargo fmt # Auto-formatting...
cargo clippy --fix --workspace --all-targets --all-features # Linting...
cargo test --all # Testing...
cargo test # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
@@ -91,27 +91,27 @@ The vast majority of the code, including all lint rules, lives in the `ruff` cra
At time of writing, the repository includes the following crates:
* `crates/ruff`: library crate containing all lint rules and the core logic for running them.
* `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
* `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
* `crates/ruff_macros`: library crate containing macros used by Ruff.
* `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
* `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule
At a high level, the steps involved in adding a new lint rule are as follows:
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
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
1. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
1. 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).
6. Add a test fixture.
7. Update the generated files (documentation and generated code).
1. Add a test fixture.
1. 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
@@ -135,7 +135,7 @@ Run `cargo dev generate-all` to generate the code for your new fixture. Then run
locally with (e.g.) `cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test --all`.
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test`.
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
generated snapshot, then commit the snapshot file alongside the rest of your changes.
@@ -147,11 +147,14 @@ The rule name should make sense when read as "allow _rule-name_" or "allow _rule
This implies that rule names:
* should state the bad thing being checked for
- should state the bad thing being checked for
* should not contain instructions on what you what you should use instead
- 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)
When re-implementing rules from other linters, this convention is given more importance than
preserving the original rule name.
### Example: Adding a new configuration option
Ruff's user-facing settings live in a few different places.
@@ -188,13 +191,13 @@ To preview any changes to the documentation locally:
pip install -r docs/requirements.txt
```
2. Generate the MkDocs site with:
1. Generate the MkDocs site with:
```shell
python scripts/generate_mkdocs.py
```
3. Run the development server with:
1. Run the development server with:
```shell
mkdocs serve

241
Cargo.lock generated
View File

@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "adler"
version = "1.0.2"
@@ -86,7 +92,7 @@ version = "2.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e"
dependencies = [
"bstr 1.2.0",
"bstr 1.3.0",
"doc-comment",
"predicates",
"predicates-core",
@@ -175,9 +181,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f0778972c64420fdedc63f09919c8a88bda7b25135357fd25a5d9f3257e832"
checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1"
dependencies = [
"memchr",
"once_cell",
@@ -298,9 +304,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.1.1"
version = "4.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75"
checksum = "bd125be87bf4c255ebc50de0b7f4d2a6201e8ac3dc86e39c0ad081dc5e7236fe"
dependencies = [
"clap 4.1.6",
]
@@ -318,9 +324,9 @@ dependencies = [
[[package]]
name = "clap_complete_fig"
version = "4.1.0"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf0c76d8fcf782a1102ccfcd10ca8246e7fdd609c1cd6deddbb96cb638e9bb5c"
checksum = "63a06158a72dbb088f864887b4409fd22600ba379f3cee3040f842234cc5c2d0"
dependencies = [
"clap 4.1.6",
"clap_complete",
@@ -550,9 +556,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -562,9 +568,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
@@ -577,15 +583,26 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
@@ -753,7 +770,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.252"
version = "0.0.254"
dependencies = [
"anyhow",
"clap 4.1.6",
@@ -840,7 +857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr 1.2.0",
"bstr 1.3.0",
"fnv",
"log",
"regex",
@@ -990,9 +1007,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.26.0"
version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f0f08b46e4379744de2ab67aa8f7de3ffd1da3e275adc41fcc82053ede46ff"
checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099"
dependencies = [
"console",
"lazy_static",
@@ -1024,10 +1041,23 @@ dependencies = [
]
[[package]]
name = "is-terminal"
version = "0.4.3"
name = "is-macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
dependencies = [
"Inflector",
"pmutil",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "is-terminal"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
@@ -1050,12 +1080,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "joinery"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
[[package]]
name = "js-sys"
version = "0.3.61"
@@ -1162,7 +1186,7 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
source = "git+https://github.com/charliermarsh/LibCST?rev=80e4c1399f95e5beb532fdd1e209ad2dbb470438#80e4c1399f95e5beb532fdd1e209ad2dbb470438"
dependencies = [
"chic",
"itertools",
@@ -1177,7 +1201,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
source = "git+https://github.com/charliermarsh/LibCST?rev=80e4c1399f95e5beb532fdd1e209ad2dbb470438#80e4c1399f95e5beb532fdd1e209ad2dbb470438"
dependencies = [
"quote",
"syn",
@@ -1283,14 +1307,14 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.42.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -1375,7 +1399,6 @@ dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
@@ -1385,7 +1408,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
@@ -1417,32 +1439,11 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.17.0"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "oorandom"
@@ -1677,6 +1678,17 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "pmutil"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1716,16 +1728,6 @@ dependencies = [
"termtree",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -1901,6 +1903,28 @@ dependencies = [
"winapi",
]
[[package]]
name = "result-like"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc7ce6435c33898517a30e85578cd204cbb696875efb93dec19a2d31294f810"
dependencies = [
"result-like-derive",
]
[[package]]
name = "result-like-derive"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn",
"syn-ext",
]
[[package]]
name = "ring"
version = "0.16.20"
@@ -1918,7 +1942,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.252"
version = "0.0.254"
dependencies = [
"anyhow",
"bisection",
@@ -1930,6 +1954,7 @@ dependencies = [
"console_error_panic_hook",
"console_log",
"criterion",
"derivative",
"dirs",
"fern",
"getrandom",
@@ -1938,6 +1963,7 @@ dependencies = [
"ignore",
"imperative",
"insta",
"is-macro",
"itertools",
"js-sys",
"libcst",
@@ -1949,8 +1975,11 @@ dependencies = [
"once_cell",
"path-absolutize",
"regex",
"result-like",
"ruff_cache",
"ruff_macros",
"ruff_python",
"ruff_rustpython",
"rustc-hash",
"rustpython-common",
"rustpython-parser",
@@ -1965,15 +1994,25 @@ dependencies = [
"test-case",
"textwrap",
"thiserror",
"titlecase",
"toml",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "ruff_cache"
version = "0.0.0"
dependencies = [
"filetime",
"globset",
"itertools",
"regex",
"ruff_macros",
]
[[package]]
name = "ruff_cli"
version = "0.0.252"
version = "0.0.254"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1999,6 +2038,7 @@ dependencies = [
"rayon",
"regex",
"ruff",
"ruff_cache",
"rustc-hash",
"serde",
"serde_json",
@@ -2073,8 +2113,12 @@ dependencies = [
"anyhow",
"clap 4.1.6",
"insta",
"is-macro",
"itertools",
"once_cell",
"ruff_formatter",
"ruff_python",
"ruff_rustpython",
"ruff_testing_macros",
"ruff_text_size",
"rustc-hash",
@@ -2084,6 +2128,16 @@ dependencies = [
"test-case",
]
[[package]]
name = "ruff_rustpython"
version = "0.0.0"
dependencies = [
"anyhow",
"once_cell",
"rustpython-common",
"rustpython-parser",
]
[[package]]
name = "ruff_testing_macros"
version = "0.0.0"
@@ -2150,7 +2204,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=edf5995a1e4c366976304ca05432dd27c913054e#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython.git?rev=822f6a9fa6b9142413634858c4c6908f678ce887#822f6a9fa6b9142413634858c4c6908f678ce887"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2159,7 +2213,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=edf5995a1e4c366976304ca05432dd27c913054e#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython.git?rev=822f6a9fa6b9142413634858c4c6908f678ce887#822f6a9fa6b9142413634858c4c6908f678ce887"
dependencies = [
"ascii",
"bitflags",
@@ -2184,24 +2238,21 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=edf5995a1e4c366976304ca05432dd27c913054e#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython.git?rev=822f6a9fa6b9142413634858c4c6908f678ce887#822f6a9fa6b9142413634858c4c6908f678ce887"
dependencies = [
"bincode",
"bitflags",
"bstr 0.2.17",
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"num_enum",
"serde",
"thiserror",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=edf5995a1e4c366976304ca05432dd27c913054e#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython.git?rev=822f6a9fa6b9142413634858c4c6908f678ce887#822f6a9fa6b9142413634858c4c6908f678ce887"
dependencies = [
"ahash",
"anyhow",
@@ -2216,7 +2267,7 @@ dependencies = [
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
"thiserror",
"serde",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
@@ -2472,15 +2523,24 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.107"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn-ext"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b86cb2b68c5b3c078cac02588bc23f3c04bb828c5d3aedd17980876ec6a7be6"
dependencies = [
"syn",
]
[[package]]
name = "tempfile"
version = "3.3.0"
@@ -2662,17 +2722,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "titlecase"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042"
dependencies = [
"joinery",
"lazy_static",
"regex",
]
[[package]]
name = "toml"
version = "0.6.0"
@@ -2858,9 +2907,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?rev=4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
[[package]]
name = "untrusted"

View File

@@ -9,12 +9,13 @@ rust-version = "1.67.0"
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
is-macro = { version = "0.2.2" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "edf5995a1e4c366976304ca05432dd27c913054e" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "edf5995a1e4c366976304ca05432dd27c913054e" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "822f6a9fa6b9142413634858c4c6908f678ce887" }
rustpython-parser = { features = ["lalrpop", "serde"], git = "https://github.com/RustPython/RustPython.git", rev = "822f6a9fa6b9142413634858c4c6908f678ce887" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }

609
README.md
View File

@@ -24,17 +24,17 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
* ⚡️ 10-100x faster than existing linters
* 🐍 Installable via `pip`
* 🛠️ `pyproject.toml` support
* 🤝 Python 3.11 compatibility
* 📦 Built-in caching, to avoid re-analyzing unchanged files
* 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
* 📏 Over [400 built-in rules](https://beta.ruff.rs/docs/rules/) (and growing)
* ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
* 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
* ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
* 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](#pyprojecttoml-discovery)
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
@@ -47,11 +47,11 @@ all while executing tens or hundreds of times faster than any individual tool.
Ruff is extremely actively developed and used in major open-source projects like:
* [pandas](https://github.com/pandas-dev/pandas)
* [FastAPI](https://github.com/tiangolo/fastapi)
* [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
* [Apache Airflow](https://github.com/apache/airflow)
* [SciPy](https://github.com/scipy/scipy)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
...and many more.
@@ -97,21 +97,19 @@ developer of [Zulip](https://github.com/zulip/zulip):
For more, see the [documentation](https://beta.ruff.rs/docs/).
1. [Installation and Usage](#installation-and-usage)
2. [Configuration](#configuration)
3. [Supported Rules](#supported-rules)
4. [Contributing](#contributing)
5. [Support](#support)
6. [Acknowledgements](#acknowledgements)
7. [Who's Using Ruff?](#whos-using-ruff)
8. [License](#license)
1. [Getting Started](#getting-started)
1. [Configuration](#configuration)
1. [Rules](#rules)
1. [Contributing](#contributing)
1. [Support](#support)
1. [Acknowledgements](#acknowledgements)
1. [Who's Using Ruff?](#whos-using-ruff)
1. [License](#license)
## Installation and Usage
## Getting Started
For more, see the [documentation](https://beta.ruff.rs/docs/).
<!-- Begin section: Installation and Usage -->
### Installation
Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
@@ -120,31 +118,8 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
pip install ruff
```
For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew:
```shell
brew install ruff
```
For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
```shell
conda install -c conda-forge ruff
```
For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/community/x86_64/ruff/) on the official repositories:
```shell
pacman -S ruff
```
For **Alpine** users, Ruff is also available as [`ruff`](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/ruff) on the testing repositories:
```shell
apk add ruff
```
[![Packaging status](https://repology.org/badge/vertical-allrepos/ruff-python-linter.svg?exclude_unsupported=1)](https://repology.org/project/ruff-python-linter/versions)
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
and with [a variety of other package managers](https://beta.ruff.rs/docs/installation/).
### Usage
@@ -157,51 +132,30 @@ ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`
ruff check path/to/code/to/file.py # Lint `file.py`
```
You can run Ruff in `--watch` mode to automatically re-run on-change:
```shell
ruff check path/to/code/ --watch
```
Ruff also works with [pre-commit](https://pre-commit.com):
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.252'
rev: 'v0.0.254'
hooks:
- id: ruff
```
Or, to enable autofix:
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.252'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
```
### Configuration
<!-- End section: Installation and Usage -->
## Configuration
<!-- Begin section: Configuration -->
Ruff can be configured via a `pyproject.toml` file, a `ruff.toml` file, or through the command line.
For a complete enumeration of the available configuration options, see the
[documentation](https://beta.ruff.rs/docs/settings/).
### Configure via `pyproject.toml`
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
[_Configuration_](https://beta.ruff.rs/docs/configuration/), or [_Settings_](https://beta.ruff.rs/docs/settings/)
for a complete list of all configuration options).
If left unspecified, the default configuration is equivalent to:
```toml
[tool.ruff]
# Enable Pyflakes `E` and `F` codes by default.
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
ignore = []
@@ -232,7 +186,6 @@ exclude = [
"node_modules",
"venv",
]
per-file-ignores = {}
# Same as Black.
line-length = 88
@@ -248,428 +201,42 @@ target-version = "py310"
max-complexity = 10
```
As an example, the following would configure Ruff to: (1) enforce flake8-bugbear rules, in addition
to the defaults; (2) avoid enforcing line-length violations (`E501`); (3) avoid attempting to fix
flake8-bugbear (`B`) violations; and (3) ignore import-at-top-of-file violations (`E402`) in
`__init__.py` files:
```toml
[tool.ruff]
# Enable flake8-bugbear (`B`) rules.
select = ["E", "F", "B"]
# Never enforce `E501` (line length violations).
ignore = ["E501"]
# Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = ["B"]
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[tool.ruff.per-file-ignores]
"__init__.py" = ["E402"]
"path/to/file.py" = ["E402"]
```
Plugin configurations should be expressed as subsections, e.g.:
```toml
[tool.ruff]
# Add "Q" to the list of enabled codes.
select = ["E", "F", "Q"]
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
```
Ruff mirrors Flake8's rule code system, in which each rule code consists of a one-to-three letter
prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the rule
(e.g., `F` for Pyflakes, `E` for pycodestyle, `ANN` for flake8-annotations). The set of enabled
rules is determined by the `select` and `ignore` options, which support both the full code (e.g.,
`F401`) and the prefix (e.g., `F`).
As a special-case, Ruff also supports the `ALL` code, which enables all rules. Note that some of the
pydocstyle rules conflict (e.g., `D203` and `D211`) as they represent alternative docstring
formats. Enabling `ALL` without further configuration may result in suboptimal behavior, especially
for the pydocstyle plugin.
If you're wondering how to configure Ruff, here are some **recommended guidelines**:
* Prefer `select` and `ignore` over `extend-select` and `extend-ignore`, to make your rule set
explicit.
* Use `ALL` with discretion. Enabling `ALL` will implicitly enable new rules whenever you upgrade.
* Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example,
you might consider expanding to `select = ["E", "F", "B"]` to enable the popular flake8-bugbear
extension.
* By default, Ruff's autofix is aggressive. If you find that it's too aggressive for your liking,
consider turning off autofix for specific rules or categories (see: [FAQ](https://beta.ruff.rs/docs/faq/#ruff-tried-to-fix-something-but-it-broke-my-code-what-should-i-do)).
### Configure via `ruff.toml`
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the
`pyproject.toml` described above would be represented via the following `ruff.toml`:
```toml
# Enable flake8-bugbear (`B`) rules.
select = ["E", "F", "B"]
# Never enforce `E501` (line length violations).
ignore = ["E501"]
# Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = ["B"]
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[per-file-ignores]
"__init__.py" = ["E402"]
"path/to/file.py" = ["E402"]
```
For a full list of configurable options, see the [list of all options](https://beta.ruff.rs/docs/settings/).
### Command-line interface
Some configuration settings can be provided via the command-line, such as those related to
Some configuration options can be provided via the command-line, such as those related to
rule enablement and disablement, file discovery, logging level, and more:
```shell
ruff check path/to/code/ --select F401 --select F403 --quiet
```
See `ruff help` for more on Ruff's top-level commands:
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` for more on the
linting command.
<!-- Begin auto-generated command help. -->
```text
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] <COMMAND>
Commands:
check Run Ruff on the given files or directories (default)
rule Explain a rule
config List or describe the available configuration options
linter List all supported upstream linters
clean Clear any caches in the current directory and any subdirectories
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
Log levels:
-v, --verbose Enable verbose logging
-q, --quiet Print lint violations, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
For help with a specific command, see: `ruff help <command>`.
```
<!-- End auto-generated command help. -->
Or `ruff help check` for more on the linting command:
<!-- Begin auto-generated subcommand help. -->
```text
Run Ruff on the given files or directories (default)
Usage: ruff check [OPTIONS] [FILES]...
Arguments:
[FILES]... List of files or directories to check
Options:
--fix
Attempt to automatically fix lint violations
--show-source
Show violations with source code
--show-fixes
Show an enumeration of all autofixed lint violations
--diff
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
-w, --watch
Run in watch mode by re-running whenever files change
--fix-only
Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
--format <FORMAT>
Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
--target-version <TARGET_VERSION>
The minimum Python version that should be supported
--config <CONFIG>
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
--statistics
Show counts for every rule with at least one violation
--add-noqa
Enable automatic additions of `noqa` directives to failing lines
--show-files
See the files Ruff will be run against with the current settings
--show-settings
See the settings Ruff will use to lint a given Python file
-h, --help
Print help
Rule selection:
--select <RULE_CODE>
Comma-separated list of rule codes to enable (or ALL, to enable all rules)
--ignore <RULE_CODE>
Comma-separated list of rule codes to disable
--extend-select <RULE_CODE>
Like --select, but adds additional rule codes on top of the selected ones
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--fixable <RULE_CODE>
List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--unfixable <RULE_CODE>
List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
File selection:
--exclude <FILE_PATTERN> List of paths, used to omit files and/or directories from analysis
--extend-exclude <FILE_PATTERN> Like --exclude, but adds additional files and directories on top of those already excluded
--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
Miscellaneous:
-n, --no-cache
Disable cache reads
--isolated
Ignore all configuration files
--cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=]
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
-e, --exit-zero
Exit with status code "0", even upon detecting lint violations
--exit-non-zero-on-fix
Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain
Log levels:
-v, --verbose Enable verbose logging
-q, --quiet Print lint violations, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
```
<!-- End auto-generated subcommand help. -->
### `pyproject.toml` discovery
Similar to [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
Ruff supports hierarchical configuration, such that the "closest" `pyproject.toml` file in the
directory hierarchy is used for every individual file, with all paths in the `pyproject.toml` file
(e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing the
`pyproject.toml` file.
There are a few exceptions to these rules:
1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any
`pyproject.toml` files that lack a `[tool.ruff]` section.
2. If a configuration file is passed directly via `--config`, those settings are used for across
files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are
resolved relative to the _current working directory_.
3. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
a default configuration. If a user-specific configuration file exists
at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default
configuration, with `${config_dir}` being determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html)
crate, and all relative paths being again resolved relative to the _current working directory_.
4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
`--select`) will override the settings in _every_ resolved configuration file.
Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
Ruff does not merge settings across configuration files; instead, the "closest" configuration file
is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff
supports an [`extend`](https://beta.ruff.rs/docs/settings#extend) field, which allows you to inherit the settings from another
`pyproject.toml` file, like so:
```toml
# Extend the `pyproject.toml` file in the parent directory.
extend = "../pyproject.toml"
# But use a different line length.
line-length = 100
```
All of the above rules apply equivalently to `ruff.toml` files. If Ruff detects both a `ruff.toml`
and `pyproject.toml` file, it will defer to the `ruff.toml`.
### Python file discovery
When passed a path on the command-line, Ruff will automatically discover all Python files in that
path, taking into account the [`exclude`](https://beta.ruff.rs/docs/settings#exclude) and
[`extend-exclude`](https://beta.ruff.rs/docs/settings#extend-exclude) settings in each directory's
`pyproject.toml` file.
By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`,
`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](https://beta.ruff.rs/docs/settings#respect-gitignore)).
Files that are passed to `ruff` directly are always linted, regardless of the above criteria.
For example, `ruff check /path/to/excluded/file.py` will always lint `file.py`.
### Rule resolution
The set of enabled rules is controlled via the [`select`](https://beta.ruff.rs/docs/settings#select)
and [`ignore`](https://beta.ruff.rs/docs/settings#ignore) settings, along with the
[`extend-select`](https://beta.ruff.rs/docs/settings#extend-select) and
[`extend-ignore`](https://beta.ruff.rs/docs/settings#extend-ignore) modifiers.
To resolve the enabled rule set, Ruff may need to reconcile `select` and `ignore` from a variety
of sources, including the current `pyproject.toml`, any inherited `pyproject.toml` files, and the
CLI (e.g., `--select`).
In those scenarios, Ruff uses the "highest-priority" `select` as the basis for the rule set, and
then applies any `extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options are given
higher priority than `pyproject.toml` options, and the current `pyproject.toml` file is given higher
priority than any inherited `pyproject.toml` files.
For example, given the following `pyproject.toml` file:
```toml
[tool.ruff]
select = ["E", "F"]
ignore = ["F401"]
```
Running `ruff check --select F401` would result in Ruff enforcing `F401`, and no other rules.
Running `ruff check --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, with
the exception of `F401`.
### Suppressing errors
To omit a lint rule entirely, add it to the "ignore" list via [`ignore`](https://beta.ruff.rs/docs/settings#ignore)
or [`extend-ignore`](https://beta.ruff.rs/docs/settings#extend-ignore), either on the command-line
or in your `pyproject.toml` file.
To ignore a violation inline, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual violation, add `# noqa: {code}` to the end of the line, like so:
```python
# Ignore F841.
x = 1 # noqa: F841
# Ignore E741 and F841.
i = 1 # noqa: E741, F841
# Ignore _all_ violations.
x = 1 # noqa
```
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
will apply to the entire string, like so:
```python
"""Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
""" # noqa: E501
```
To ignore all violations across an entire file, add `# ruff: noqa` to any line in the file, like so:
```python
# ruff: noqa
```
To ignore a specific rule across an entire file, add `# ruff: noqa: {code}` to any line in the file,
like so:
```python
# ruff: noqa: F841
```
Or see the [`per-file-ignores`](https://beta.ruff.rs/docs/settings#per-file-ignores) configuration
setting, which enables the same functionality via a `pyproject.toml` file.
Note that Ruff will also respect Flake8's `# flake8: noqa` directive, and will treat it as
equivalent to `# ruff: noqa`.
#### Automatic error suppression
Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special rule code, `RUF100`, to enforce that your `noqa` directives are
"valid", in that the violations they _say_ they ignore are actually being triggered on that line (and
thus suppressed). You can run `ruff check /path/to/file.py --extend-select RUF100` to flag unused `noqa`
directives.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
You can run `ruff check /path/to/file.py --extend-select RUF100 --fix` to automatically remove unused
`noqa` directives.
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. You can run `ruff check /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate rule codes.
#### Action comments
Ruff respects `isort`'s [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which
enable selectively enabling and disabling import sorting for blocks of code and other inline
configuration.
See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
for more.
### Exit codes
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.
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.
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
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.
### Autocompletion
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
by `ruff generate-shell-completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,
`powershell`, or `zsh`.
The exact steps required to enable autocompletion will vary by shell. For example instructions,
see the [Poetry](https://python-poetry.org/docs/#enable-tab-completion-for-bash-fish-or-zsh) or
[ripgrep](https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#complete) documentation.
As an example: to enable autocompletion for Zsh, run
`ruff generate-shell-completion zsh > ~/.zfunc/_ruff`. Then add the following line to your
`~/.zshrc` file, if they're not already present:
```zsh
fpath+=~/.zfunc
autoload -Uz compinit && compinit
```
<!-- End section: Configuration -->
## Supported Rules
## Rules
<!-- Begin section: Rules -->
Ruff supports over 400 lint rules, many of which are inspired by popular tools like Flake8, isort,
pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
**Ruff supports over 500 lint rules**, many of which are inspired by popular tools like Flake8,
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
Rust as a first-party feature.
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, omitting those
stylistic rules made obsolete by the use of an autoformatter, like [Black](https://github.com/psf/black).
stylistic rules made obsolete by the use of an autoformatter, like
[Black](https://github.com/psf/black).
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
<!-- End section: Rules -->
For a complete enumeration, see the [list of rules](https://beta.ruff.rs/docs/rules/) in the
Ruff documentation.
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
## Contributing
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md). You
can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
[**contributing guidelines**](https://beta.ruff.rs/docs/contributing/).
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Support
@@ -678,8 +245,6 @@ or feel free to [**open a new one**](https://github.com/charliermarsh/ruff/issue
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
<!-- Begin section: Acknowledgements -->
## Acknowledgements
Ruff's linter draws on both the APIs and implementation details of many other
@@ -702,47 +267,45 @@ Ruff is the beneficiary of a large number of [contributors](https://github.com/c
Ruff is released under the MIT license.
<!-- End section: Acknowledgements -->
## Who's Using Ruff?
Ruff is used in a number of major open-source projects, including:
* [pandas](https://github.com/pandas-dev/pandas)
* [FastAPI](https://github.com/tiangolo/fastapi)
* [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
* [Diffusers (Hugging Face)](https://github.com/huggingface/diffusers)
* [Apache Airflow](https://github.com/apache/airflow)
* [SciPy](https://github.com/scipy/scipy)
* [Zulip](https://github.com/zulip/zulip)
* [Bokeh](https://github.com/bokeh/bokeh)
* [Pydantic](https://github.com/pydantic/pydantic)
* [Dagster](https://github.com/dagster-io/dagster)
* [Dagger](https://github.com/dagger/dagger)
* [Sphinx](https://github.com/sphinx-doc/sphinx)
* [Hatch](https://github.com/pypa/hatch)
* [PDM](https://github.com/pdm-project/pdm)
* [Jupyter](https://github.com/jupyter-server/jupyter_server)
* [Great Expectations](https://github.com/great-expectations/great_expectations)
* [ONNX](https://github.com/onnx/onnx)
* [Polars](https://github.com/pola-rs/polars)
* [Ibis](https://github.com/ibis-project/ibis)
* [Synapse (Matrix)](https://github.com/matrix-org/synapse)
* [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
* [Dispatch (Netflix)](https://github.com/Netflix/dispatch)
* [Saleor](https://github.com/saleor/saleor)
* [Pynecone](https://github.com/pynecone-io/pynecone)
* [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
* [Home Assistant](https://github.com/home-assistant/core)
* [Pylint](https://github.com/PyCQA/pylint)
* [Cryptography (PyCA)](https://github.com/pyca/cryptography)
* [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
* [build (PyPA)](https://github.com/pypa/build)
* [Babel](https://github.com/python-babel/babel)
* [featuretools](https://github.com/alteryx/featuretools)
* [meson-python](https://github.com/mesonbuild/meson-python)
* [ZenML](https://github.com/zenml-io/zenml)
* [delta-rs](https://github.com/delta-io/delta-rs)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Diffusers (Hugging Face)](https://github.com/huggingface/diffusers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
- [Zulip](https://github.com/zulip/zulip)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Dagster](https://github.com/dagster-io/dagster)
- [Dagger](https://github.com/dagger/dagger)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Hatch](https://github.com/pypa/hatch)
- [PDM](https://github.com/pdm-project/pdm)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- [ONNX](https://github.com/onnx/onnx)
- [Polars](https://github.com/pola-rs/polars)
- [Ibis](https://github.com/ibis-project/ibis)
- [Synapse (Matrix)](https://github.com/matrix-org/synapse)
- [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
- [Dispatch (Netflix)](https://github.com/Netflix/dispatch)
- [Saleor](https://github.com/saleor/saleor)
- [Pynecone](https://github.com/pynecone-io/pynecone)
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
- [Home Assistant](https://github.com/home-assistant/core)
- [Pylint](https://github.com/PyCQA/pylint)
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
- [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
- [build (PyPA)](https://github.com/pypa/build)
- [Babel](https://github.com/python-babel/babel)
- [featuretools](https://github.com/alteryx/featuretools)
- [meson-python](https://github.com/mesonbuild/meson-python)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
## License

View File

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

View File

@@ -84,7 +84,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
2. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.252"
version = "0.0.254"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = { workspace = true }
rust-version = { workspace = true }
@@ -16,6 +16,11 @@ crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" }
ruff_cache = { path = "../ruff_cache" }
anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" }
@@ -23,12 +28,14 @@ cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { workspace = true, features = ["derive", "env", "string"] }
colored = { version = "2.0.0" }
derivative = { version = "2.2.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
is-macro = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
log = { version = "0.4.17" }
@@ -39,8 +46,7 @@ num-traits = "0.2.15"
once_cell = { workspace = true }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { workspace = true }
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
result-like = "0.4.6"
rustc-hash = { workspace = true }
rustpython-common = { workspace = true }
rustpython-parser = { workspace = true }
@@ -53,7 +59,6 @@ strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml = { workspace = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support

View File

@@ -107,3 +107,7 @@ class Foo:
# ANN101
def foo(self, /, a: int, b: int) -> int:
pass
# OK
def f(*args: *tuple[int]) -> None: ...

View File

@@ -19,6 +19,8 @@ token = "s3cr3t"
secrete = "s3cr3t"
safe = password = "s3cr3t"
password = safe = "s3cr3t"
PASSWORD = "s3cr3t"
PassWord = "s3cr3t"
d["password"] = "s3cr3t"
d["pass"] = "s3cr3t"
@@ -61,3 +63,15 @@ if token == "3\t4":
if token == "5\r6":
pass
# These should not be flagged
passed_msg = "You have passed!"
compassion = "Please don't match!"
impassable = "You shall not pass!"
passwords = ""
PASSWORDS = ""
passphrases = ""
PassPhrases = ""
tokens = ""
secrets = ""

View File

@@ -59,6 +59,10 @@ getattr(someobj, attrname, False)
mylist.index(True)
int(True)
str(int(False))
cfg.get("hello", True)
cfg.getint("hello", True)
cfg.getfloat("hello", True)
cfg.getboolean("hello", True)
class Registry:

View File

@@ -626,3 +626,8 @@ result = function(
bar,
**{'ham': spam}
)
# Make sure the COM812 and UP034 rules don't autofix simultaneously and cause a syntax error.
the_first_one = next(
(i for i in range(10) if i // 2 == 0) # COM812 fix should include the final bracket
)

View File

@@ -0,0 +1,21 @@
from django.shortcuts import render
def test_view1(request):
return render(request, "index.html", locals())
def test_view2(request):
return render(request, "index.html", context=locals())
def test_view3(request):
return render(request, "index.html")
def test_view4(request):
return render(request, "index.html", {})
def test_view5(request):
return render(request, "index.html", context={})

View File

@@ -0,0 +1,11 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
exclude = ["bar"]
class TestModelForm2(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -0,0 +1,16 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
fields = "__all__"
class TestModelForm2(forms.ModelForm):
class Meta:
fields = b"__all__"
class TestModelForm3(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -0,0 +1,10 @@
# no error
all((x.id for x in bar))
all(x.id for x in bar)
all(x.id for x in bar)
any(x.id for x in bar)
any({x.id for x in bar})
# PIE 802
any([x.id for x in bar])
all([x.id for x in bar])

View File

@@ -0,0 +1,14 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info <= (3, 10): ... # OK
if sys.version_info > (3, 10): ... # OK
if python_version > (3, 10): ... # OK

View File

@@ -0,0 +1,18 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info == (3, 9): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if python_version > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons

View File

@@ -0,0 +1,14 @@
def bar():
... # OK
def foo():
pass # OK, since we're not in a stub file
class Bar:
... # OK
class Foo:
pass # OK, since we're not in a stub file

View File

@@ -0,0 +1,8 @@
def bar(): ... # OK
def foo():
pass # ERROR PYI009, since we're in a stub file
class Bar: ... # OK
class Foo:
pass # ERROR PYI009, since we're in a stub file

View File

@@ -0,0 +1,18 @@
def bar():
... # OK
def foo():
"""foo""" # OK
def buzz():
print("buzz") # OK, not in stub file
def foo2():
123 # OK, not in a stub file
def bizz():
x = 123 # OK, not in a stub file

View File

@@ -0,0 +1,12 @@
def bar(): ... # OK
def foo():
"""foo""" # OK, strings are handled by another rule
def buzz():
print("buzz") # ERROR PYI010
def foo2():
123 # ERROR PYI010
def bizz():
x = 123 # ERROR PYI010

View File

@@ -0,0 +1,79 @@
def f12(
x,
y: str = os.pathsep, # OK
) -> None:
...
def f11(*, x: str = "x") -> None: # OK
...
def f13(
x: list[str] = [
"foo",
"bar",
"baz",
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
) # OK
) -> None:
...
def f15(
x: set[str] = {
"foo",
"bar",
"baz",
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" + 4, # OK
) -> None:
...
def f20(
x: int = 5 + 5, # OK
) -> None:
...
def f21(
x: complex = 3j - 3j, # OK
) -> None:
...
def f22(
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -0,0 +1,63 @@
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
def f13(
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(
x: frozenset[
bytes
] = frozenset( # Error PYI011 Only simple default values allowed for typed arguments
{b"foo", b"bar", b"baz"}
)
) -> None: ...
def f17(
x: str = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ "bar",
) -> None: ...
def f18(
x: str = b"foo" # Error PYI011 Only simple default values allowed for typed arguments
+ b"bar",
) -> None: ...
def f19(
x: object = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ 4,
) -> None: ...
def f20(
x: int = 5
+ 5, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f21(
x: complex = 3j
- 3j, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...

View File

@@ -0,0 +1,79 @@
def f12(
x,
y=os.pathsep, # OK
) -> None:
...
def f11(*, x="x") -> None:
... # OK
def f13(
x=[ # OK
"foo",
"bar",
"baz",
]
) -> None:
...
def f14(
x=( # OK
"foo",
"bar",
"baz",
)
) -> None:
...
def f15(
x={ # OK
"foo",
"bar",
"baz",
}
) -> None:
...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
... # OK
def f17(
x="foo" + "bar", # OK
) -> None:
...
def f18(
x=b"foo" + b"bar", # OK
) -> None:
...
def f19(
x="foo" + 4, # OK
) -> None:
...
def f20(
x=5 + 5, # OK
) -> None:
...
def f21(
x=3j - 3j, # OK
) -> None:
...
def f22(
x=-42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -0,0 +1,45 @@
def f12(
x,
y=os.pathsep, # Error PYI014
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # Error PYI014
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
def f17(
x="foo" + "bar", # Error PYI014
) -> None: ...
def f18(
x=b"foo" + b"bar", # Error PYI014
) -> None: ...
def f19(
x="foo" + 4, # Error PYI014
) -> None: ...
def f20(
x=5 + 5, # Error PYI014
) -> None: ...
def f21(
x=3j - 3j, # Error PYI014
) -> None: ...
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...

View File

@@ -0,0 +1,14 @@
"""foo""" # OK, not in stub
def foo():
"""foo""" # OK, doc strings are allowed in non-stubs
class Bar:
"""bar""" # OK, doc strings are allowed in non-stubs
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -0,0 +1,11 @@
"""foo""" # ERROR PYI021
def foo():
"""foo""" # ERROR PYI021
class Bar:
"""bar""" # ERROR PYI021
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -0,0 +1,33 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -0,0 +1,33 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -23,7 +23,7 @@ def test_error():
"""
# recursive case
assert not (a or not (b or c)) # note that we only reduce once here
assert not (a or not (b or c))
assert not (a or not (b and c))
# detected, but no autofix for messages

View File

@@ -293,3 +293,30 @@ def x(y):
def foo(baz: str) -> str:
return baz
def end_of_statement():
def example():
if True:
return ""
def example():
if True:
return ""
def example():
if True:
return "" # type: ignore
def example():
if True:
return "" ;
def example():
if True:
return "" \
; # type: ignore

View File

@@ -74,3 +74,13 @@ elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"

View File

@@ -11,6 +11,7 @@ YODA > age # SIM300
YODA >= age # SIM300
JediOrder.YODA == age # SIM300
0 < (number - 100) # SIM300
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # SIM300
# OK
compare == "yoda"

View File

@@ -1,7 +1,8 @@
from typing import TYPE_CHECKING, Any, ClassVar
import attrs
from ....import unknown
from ..protocol import commands, definitions, responses
from ..server import example
from .. import server

View File

@@ -0,0 +1,15 @@
from foo import ( # Comment on `foo`
Member as Alias, # Comment on `Alias`
)
from bar import ( # Comment on `bar`
Member, # Comment on `Member`
)
from baz import ( # Comment on `baz`
Member as Alias # Comment on `Alias`
)
from bop import ( # Comment on `bop`
Member # Comment on `Member`
)

View File

@@ -0,0 +1,14 @@
#: E211
spam (1)
#: E211 E211
dict ['key'] = list [index]
#: E211
dict['key'] ['subkey'] = list[index]
#: Okay
spam(1)
dict['key'] = list[index]
# This is not prohibited by PEP8, but avoid it.
class Foo (Bar, Baz):
pass

View File

@@ -0,0 +1,50 @@
#: E251 E251
def foo(bar = False):
'''Test function with an error in declaration'''
pass
#: E251
foo(bar= True)
#: E251
foo(bar =True)
#: E251 E251
foo(bar = True)
#: E251
y = bar(root= "sdasd")
#: E251:2:29
parser.add_argument('--long-option',
default=
"/rather/long/filesystem/path/here/blah/blah/blah")
#: E251:1:45
parser.add_argument('--long-option', default
="/rather/long/filesystem/path/here/blah/blah/blah")
#: E251:3:8 E251:3:10
foo(True,
baz=(1, 2),
biz = 'foo'
)
#: Okay
foo(bar=(1 == 1))
foo(bar=(1 != 1))
foo(bar=(1 >= 1))
foo(bar=(1 <= 1))
(options, args) = parser.parse_args()
d[type(None)] = _deepcopy_atomic
# Annotated Function Definitions
#: Okay
def munge(input: AnyStr, sep: AnyStr = None, limit=1000,
extra: Union[str, dict] = None) -> AnyStr:
pass
#: Okay
async def add(a: int = 0, b: int = 0) -> int:
return a + b
# Previously E251 four times
#: E271:1:6
async def add(a: int = 0, b: int = 0) -> int:
return a + b
#: E252:1:15 E252:1:16 E252:1:27 E252:1:36
def add(a: int=0, b: int =0, c: int= 0) -> int:
return a + b + c
#: Okay
def add(a: int = _default(name='f')):
return a

View File

@@ -0,0 +1,145 @@
#: W191
if False:
print # indented with 1 tab
#:
#: W191
y = x == 2 \
or x == 3
#: E101 W191 W504
if (
x == (
3
) or
y == 4):
pass
#: E101 W191
if x == 2 \
or y > 1 \
or x == 3:
pass
#: E101 W191
if x == 2 \
or y > 1 \
or x == 3:
pass
#:
#: E101 W191 W504
if (foo == bar and
baz == bop):
pass
#: E101 W191 W504
if (
foo == bar and
baz == bop
):
pass
#:
#: E101 E101 W191 W191
if start[1] > end_col and not (
over_indent == 4 and indent_next):
return (0, "E121 continuation line over-"
"indented for visual indent")
#:
#: E101 W191
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
#: E101 W191 W504
if ((row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col)):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
#: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
if bar:
return (
start, 'E121 lines starting with a '
'closing bracket should be indented '
"to match that of the opening "
"bracket's line"
)
#
#: E101 W191 W504
# you want vertical alignment, so use a parens
if ((foo.bar("baz") and
foo.bar("bop")
)):
print "yes"
#: E101 W191 W504
# also ok, but starting to look like LISP
if ((foo.bar("baz") and
foo.bar("bop"))):
print "yes"
#: E101 W191 W504
if (a == 2 or
b == "abc def ghi"
"jkl mno"):
return True
#: E101 W191 W504
if (a == 2 or
b == """abc def ghi
jkl mno"""):
return True
#: W191:2:1 W191:3:1 E101:3:2
if length > options.max_line_length:
return options.max_line_length, \
"E501 line too long (%d characters)" % length
#
#: E101 W191 W191 W504
if os.path.exists(os.path.join(path, PEP8_BIN)):
cmd = ([os.path.join(path, PEP8_BIN)] +
self._pep8_options(targetfile))
#: W191
'''
multiline string with tab in it'''
#: E101 W191
'''multiline string
with tabs
and spaces
'''
#: Okay
'''sometimes, you just need to go nuts in a multiline string
and allow all sorts of crap
like mixed tabs and spaces
or trailing whitespace
or long long long long long long long long long long long long long long long long long lines
''' # nopep8
#: Okay
'''this one
will get no warning
even though the noqa comment is not immediately after the string
''' + foo # noqa
#
#: E101 W191
if foo is None and bar is "bop" and \
blah == 'yeah':
blah = 'yeahnah'
#
#: W191 W191 W191
if True:
foo(
1,
2)
#: W191 W191 W191 W191 W191
def test_keys(self):
"""areas.json - All regions are accounted for."""
expected = set([
u'Norrbotten',
u'V\xe4sterbotten',
])
#: W191
x = [
'abc'
]
#:

View File

@@ -0,0 +1,26 @@
#: Okay
# 情
#: W291:1:6
print
#: W293:2:1
class Foo(object):
bang = 12
#: W291:2:35
'''multiline
string with trailing whitespace'''
#: W291 W292 noeol
x = 1
#: W191 W292 noeol
if False:
pass # indented with tabs
#: W292:1:36 noeol
# This line doesn't have a linefeed
#: W292:1:5 E225:1:2 noeol
1+ 1
#: W292:1:27 E261:1:12 noeol
import this # no line feed
#: W292:3:22 noeol
class Test(object):
def __repr__(self):
return 'test'

View File

@@ -16,21 +16,17 @@ if False == None: # E711, E712 (fix)
if None == False: # E711, E712 (fix)
pass
###
# Unfixable errors
###
if "abc" == None: # E711
pass
if None == "abc": # E711
pass
if "abc" == False: # E712
pass
if False == "abc": # E712
pass
###
# Non-errors
###
if "abc" == None:
pass
if None == "abc":
pass
if "abc" == False:
pass
if False == "abc":
pass
if "def" == "abc":
pass
if False is None:

View File

@@ -2,6 +2,8 @@
from functools import cached_property
from gi.repository import GObject
# Bad examples
def bad_liouiwnlkjl():
@@ -76,6 +78,11 @@ class Thingy:
"""This property method docstring does not need to be written in imperative mood."""
return self._beep
@GObject.Property
def good_custom_property(self):
"""This property method docstring does not need to be written in imperative mood."""
return self._beep
@cached_property
def good_cached_property(self):
"""This property method docstring does not need to be written in imperative mood."""

View File

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

View File

@@ -0,0 +1,4 @@
try:
pass
except ExceptionGroup:
pass

View File

@@ -0,0 +1,23 @@
"""Test case: strings used within calls within type annotations."""
from typing import Callable
import bpy
from mypy_extensions import VarArg
from foo import Bar
class LightShow(bpy.types.Operator):
label = "Create Character"
name = "lightshow.letter_creation"
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
def f(x: Callable[[VarArg("os")], None]): # F821
pass
f(Callable[["Bar"], None])
f(Callable[["Baz"], None])

View File

@@ -0,0 +1,25 @@
"""Test case: strings used within calls within type annotations."""
from __future__ import annotations
from typing import Callable
import bpy
from mypy_extensions import VarArg
from foo import Bar
class LightShow(bpy.types.Operator):
label = "Create Character"
name = "lightshow.letter_creation"
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
def f(x: Callable[[VarArg("os")], None]): # F821
pass
f(Callable[["Bar"], None])
f(Callable[["Baz"], None])

View File

@@ -20,7 +20,27 @@ def f(provided: int) -> int:
match provided:
case True:
return captured # F821
case [captured, *_]:
return captured
def f(provided: int) -> int:
match provided:
case captured:
return captured
def f(provided: int) -> int:
match provided:
case [captured, *_]:
return captured
def f(provided: int) -> int:
match provided:
case [*captured]:
return captured
def f(provided: int) -> int:
match provided:
case {**captured}:
return captured

View File

@@ -0,0 +1,49 @@
"""
Test for else-if-used
"""
def ok0():
"""Should not trigger on elif"""
if 1:
pass
elif 2:
pass
def ok1():
"""If the orelse has more than 1 item in it, shouldn't trigger"""
if 1:
pass
else:
print()
if 1:
pass
def ok2():
"""If the orelse has more than 1 item in it, shouldn't trigger"""
if 1:
pass
else:
if 1:
pass
print()
def not_ok0():
if 1:
pass
else:
if 2:
pass
def not_ok1():
if 1:
pass
else:
if 2:
pass
else:
pass

View File

@@ -0,0 +1,75 @@
# Adapted from:
# https://github.com/PyCQA/pylint/blob/b70d2abd7fabe9bfd735a30b593b9cd5eaa36194/tests/functional/g/globals.py
CONSTANT = 1
def FUNC():
pass
class CLASS:
pass
def fix_constant(value):
"""All this is ok, but try not to use `global` ;)"""
global CONSTANT # [global-statement]
print(CONSTANT)
CONSTANT = value
def global_with_import():
"""Should only warn for global-statement when using `Import` node"""
global sys # [global-statement]
import sys
def global_with_import_from():
"""Should only warn for global-statement when using `ImportFrom` node"""
global namedtuple # [global-statement]
from collections import namedtuple
def global_del():
"""Deleting the global name prevents `global-variable-not-assigned`"""
global CONSTANT # [global-statement]
print(CONSTANT)
del CONSTANT
def global_operator_assign():
"""Operator assigns should only throw a global statement error"""
global CONSTANT # [global-statement]
print(CONSTANT)
CONSTANT += 1
def global_function_assign():
"""Function assigns should only throw a global statement error"""
global CONSTANT # [global-statement]
def CONSTANT():
pass
CONSTANT()
def override_func():
"""Overriding a function should only throw a global statement error"""
global FUNC # [global-statement]
def FUNC():
pass
FUNC()
def override_class():
"""Overriding a class should only throw a global statement error"""
global CLASS # [global-statement]
class CLASS:
pass
CLASS()

View File

@@ -7,6 +7,18 @@ logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls without any args
logging.info("100% dynamic")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
import warning
warning.warning("Hello %s %s", "World!")

View File

@@ -2,11 +2,19 @@ import logging
logging.warning("Hello %s", "World!", "again") # [logging-too-many-args]
# do not handle calls with kwargs (like pylint)
logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
import warning
warning.warning("Hello %s", "World!", "again")

View File

@@ -25,22 +25,7 @@ avoid-escape = true
max-complexity = 10
[tool.ruff.pep8-naming]
ignore-names = [
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
]
classmethod-decorators = ["classmethod", "pydantic.validator"]
staticmethod-decorators = ["staticmethod"]
classmethod-decorators = ["pydantic.validator"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"

View File

@@ -28,3 +28,10 @@ def f(x: IList[str]) -> None:
def f(x: "List[str]") -> None:
...
list = "abc"
def f(x: List[str]) -> None:
...

View File

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

View File

@@ -2,36 +2,42 @@ from typing import TypedDict, NotRequired, Literal
import typing
# dict literal
MyType1 = TypedDict("MyType1", {"a": int, "b": str})
MyType = TypedDict("MyType", {"a": int, "b": str})
# dict call
MyType2 = TypedDict("MyType2", dict(a=int, b=str))
MyType = TypedDict("MyType", dict(a=int, b=str))
# kwargs
MyType3 = TypedDict("MyType3", a=int, b=str)
MyType = TypedDict("MyType", a=int, b=str)
# Empty TypedDict
MyType4 = TypedDict("MyType4")
MyType = TypedDict("MyType")
# Literal values
MyType5 = TypedDict("MyType5", {"a": "hello"})
MyType6 = TypedDict("MyType6", a="hello")
MyType = TypedDict("MyType", {"a": "hello"})
MyType = TypedDict("MyType", a="hello")
# NotRequired
MyType7 = TypedDict("MyType7", {"a": NotRequired[dict]})
MyType = TypedDict("MyType", {"a": NotRequired[dict]})
# total
MyType8 = TypedDict("MyType8", {"x": int, "y": int}, total=False)
# invalid identifiers
MyType9 = TypedDict("MyType9", {"in": int, "x-y": int})
MyType = TypedDict("MyType", {"x": int, "y": int}, total=False)
# using Literal type
MyType10 = TypedDict("MyType10", {"key": Literal["value"]})
MyType = TypedDict("MyType", {"key": Literal["value"]})
# using namespace TypedDict
MyType11 = typing.TypedDict("MyType11", {"key": int})
MyType = typing.TypedDict("MyType", {"key": int})
# unpacking
# invalid identifiers (OK)
MyType = TypedDict("MyType", {"in": int, "x-y": int})
# unpacking (OK)
c = {"c": float}
MyType12 = TypedDict("MyType1", {"a": int, "b": str, **c})
MyType = TypedDict("MyType", {"a": int, "b": str, **c})
# Empty dict literal
MyType = TypedDict("MyType", {})
# Empty dict call
MyType = TypedDict("MyType", dict())

View File

@@ -2,21 +2,30 @@ from typing import NamedTuple
import typing
# with complex annotations
NT1 = NamedTuple("NT1", [("a", int), ("b", tuple[str, ...])])
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
# with default values as list
NT2 = NamedTuple(
"NT2",
MyType = NamedTuple(
"MyType",
[("a", int), ("b", str), ("c", list[bool])],
defaults=["foo", [True]],
)
# with namespace
NT3 = typing.NamedTuple("NT3", [("a", int), ("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
# with too many default values
NT4 = NamedTuple(
"NT4",
# too many default values (OK)
MyType = NamedTuple(
"MyType",
[("a", int), ("b", str)],
defaults=[1, "bar", "baz"],
)
# invalid identifiers (OK)
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
# no fields
MyType = typing.NamedTuple("MyType")
# empty fields
MyType = typing.NamedTuple("MyType", [])

View File

@@ -0,0 +1,7 @@
isinstance(1, (int, float)) # UP038
issubclass("yes", (int, float, str)) # UP038
isinstance(1, int) # OK
issubclass("yes", int) # OK
isinstance(1, int | float) # OK
issubclass("yes", int | str) # OK

View File

@@ -1,15 +0,0 @@
def f(*args, **kwargs):
pass
a = (1, 2)
b = (3, 4)
c = (5, 6)
d = (7, 8)
f(a, b)
f(a, kw=b)
f(*a, kw=b)
f(kw=a, *b)
f(kw=a, *b, *c)
f(*a, kw=b, *c, kw1=d)

View File

@@ -30,3 +30,11 @@ def good():
raise e # This is verbose violation, shouldn't trigger no cause
except Exception:
raise # Just re-raising don't need 'from'
def good():
try:
from mod import f
except ImportError:
def f():
raise MyException() # Raising within a new scope is fine

View File

@@ -32,9 +32,10 @@ pub fn classify(
checker
.resolve_call_path(map_callable(expr))
.map_or(false, |call_path| {
staticmethod_decorators
.iter()
.any(|decorator| call_path == to_call_path(decorator))
call_path.as_slice() == ["", "staticmethod"]
|| staticmethod_decorators
.iter()
.any(|decorator| call_path == to_call_path(decorator))
})
}) {
FunctionType::StaticMethod
@@ -51,6 +52,7 @@ pub fn classify(
|| decorator_list.iter().any(|expr| {
// The method is decorated with a class method decorator (like `@classmethod`).
checker.resolve_call_path(map_callable(expr)).map_or(false, |call_path| {
call_path.as_slice() == ["", "classmethod"] ||
classmethod_decorators
.iter()
.any(|decorator| call_path == to_call_path(decorator))

View File

@@ -1,5 +1,6 @@
use std::path::Path;
use bitflags::bitflags;
use itertools::Itertools;
use log::error;
use once_cell::sync::Lazy;
@@ -575,33 +576,32 @@ pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
})
}
bitflags! {
pub struct Exceptions: u32 {
const NAME_ERROR = 0b0000_0001;
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
}
}
/// Extract the names of all handled exceptions.
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<CallPath> {
// TODO(charlie): Use `resolve_call_path` to avoid false positives for
// overridden builtins.
let mut handler_names = vec![];
pub fn extract_handled_exceptions(handlers: &[Excepthandler]) -> Vec<&Expr> {
let mut handled_exceptions = Vec::new();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {
if let Some(type_) = type_ {
if let ExprKind::Tuple { elts, .. } = &type_.node {
for type_ in elts {
let call_path = collect_call_path(type_);
if !call_path.is_empty() {
handler_names.push(call_path);
}
handled_exceptions.push(type_);
}
} else {
let call_path = collect_call_path(type_);
if !call_path.is_empty() {
handler_names.push(call_path);
}
handled_exceptions.push(type_);
}
}
}
}
}
handler_names
handled_exceptions
}
/// Return the set of all bound argument names.
@@ -765,7 +765,7 @@ pub fn from_relative_import<'a>(module: &'a [String], name: &'a str) -> CallPath
call_path
}
/// A [`Visitor`] that collects all return statements in a function or method.
/// A [`Visitor`] that collects all `return` statements in a function or method.
#[derive(Default)]
pub struct ReturnStatementVisitor<'a> {
pub returns: Vec<Option<&'a Expr>>,
@@ -786,6 +786,48 @@ where
}
}
/// A [`Visitor`] that collects all `raise` statements in a function or method.
#[derive(Default)]
pub struct RaiseStatementVisitor<'a> {
pub raises: Vec<(Range, Option<&'a Expr>, Option<&'a Expr>)>,
}
impl<'a, 'b> Visitor<'b> for RaiseStatementVisitor<'b>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::Raise { exc, cause } => {
self.raises
.push((Range::from_located(stmt), exc.as_deref(), cause.as_deref()));
}
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::If { body, orelse, .. } => {
visitor::walk_body(self, body);
visitor::walk_body(self, orelse);
}
StmtKind::While { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. }
| StmtKind::For { body, .. }
| StmtKind::AsyncFor { body, .. } => {
visitor::walk_body(self, body);
}
StmtKind::Match { cases, .. } => {
for case in cases {
visitor::walk_body(self, &case.body);
}
}
_ => {}
}
}
}
/// Convert a location within a file (relative to `base`) to an absolute
/// position.
pub fn to_absolute(relative: Location, base: Location) -> Location {
@@ -1071,6 +1113,32 @@ pub fn first_colon_range(range: Range, locator: &Locator) -> Option<Range> {
range
}
/// Given a statement, find its "logical end".
///
/// For example: the statement could be following by a trailing semicolon, by an end-of-line
/// comment, or by any number of continuation lines (and then by a comment, and so on).
pub fn end_of_statement(stmt: &Stmt, locator: &Locator) -> Location {
let contents = locator.skip(stmt.end_location.unwrap());
// End-of-file, so just return the end of the statement.
if contents.is_empty() {
return stmt.end_location.unwrap();
}
// Otherwise, find the end of the last line that's "part of" the statement.
for (lineno, line) in contents.lines().enumerate() {
if line.ends_with('\\') {
continue;
}
return to_absolute(
Location::new(lineno + 1, line.chars().count()),
stmt.end_location.unwrap(),
);
}
unreachable!("Expected to find end-of-statement")
}
/// Return the `Range` of the first `Elif` or `Else` token in an `If` statement.
pub fn elif_else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> {
let StmtKind::If { body, orelse, .. } = &stmt.node else {

View File

@@ -124,7 +124,7 @@ impl<'a> Scope<'a> {
// StarImportation
// FutureImportation
#[derive(Clone, Debug)]
#[derive(Clone, Debug, is_macro::Is)]
pub enum BindingKind<'a> {
Annotation,
Argument,

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,20 @@
#![allow(dead_code, unused_imports, unused_variables)]
use bisection::bisect_left;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::registry::{Diagnostic, Rule};
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
use crate::rules::pycodestyle::rules::{
extraneous_whitespace, indentation, space_around_operator, whitespace_around_keywords,
whitespace_before_comment,
extraneous_whitespace, indentation, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
whitespace_around_named_parameter_equals, whitespace_before_comment,
whitespace_before_parameters,
};
use crate::settings::Settings;
use crate::settings::{flags, Settings};
use crate::source_code::{Locator, Stylist};
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
@@ -39,6 +43,7 @@ pub fn check_logical_lines(
locator: &Locator,
stylist: &Stylist,
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
@@ -106,6 +111,18 @@ pub fn check_logical_lines(
});
}
}
for (location, kind) in missing_whitespace_after_keyword(&line.tokens) {
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
}
if line.flags.contains(TokenFlags::COMMENT) {
for (range, kind) in whitespace_before_comment(&line.tokens, locator) {
@@ -120,6 +137,47 @@ pub fn check_logical_lines(
}
}
}
if line.flags.contains(TokenFlags::OPERATOR) {
for (location, kind) in
whitespace_around_named_parameter_equals(&line.tokens, &line.text)
{
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
for (location, kind) in missing_whitespace_around_operator(&line.tokens) {
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
}
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
let should_fix =
autofix.into() && settings.rules.should_fix(&Rule::WhitespaceBeforeParameters);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
}
}
for (index, kind) in indentation(
&line,

View File

@@ -148,9 +148,7 @@ pub fn check_noqa(
UnusedNOQA { codes: None },
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(diagnostic.kind.rule())
{
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
@@ -217,9 +215,7 @@ pub fn check_noqa(
},
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(diagnostic.kind.rule())
{
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),

View File

@@ -8,7 +8,8 @@ use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
doc_line_too_long, indentation_contains_tabs, line_too_long, mixed_spaces_and_tabs,
no_newline_at_end_of_file, trailing_whitespace,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
@@ -41,11 +42,15 @@ pub fn check_physical_lines(
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(&Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(&Rule::IndentationContainsTabs);
let fix_unnecessary_coding_comment = matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace = matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::ShebangWhitespace);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace =
autofix.into() && settings.rules.should_fix(&Rule::ShebangWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -139,14 +144,25 @@ pub fn check_physical_lines(
if enforce_bidirectional_unicode {
diagnostics.extend(pylint::rules::bidirectional_unicode(index, line));
}
if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace {
if let Some(diagnostic) = trailing_whitespace(index, line, settings, autofix) {
diagnostics.push(diagnostic);
}
}
if enforce_indentation_contains_tabs {
if let Some(diagnostic) = indentation_contains_tabs(index, line) {
diagnostics.push(diagnostic);
}
}
}
if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file(
stylist,
contents,
matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}

View File

@@ -7,8 +7,8 @@ use crate::lex::docstring_detection::StateMachine;
use crate::registry::{Diagnostic, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, pyupgrade,
ruff,
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
pyupgrade, ruff,
};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
@@ -18,6 +18,7 @@ pub fn check_tokens(
tokens: &[LexResult],
settings: &Settings,
autofix: flags::Autofix,
is_interface_definition: bool,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
@@ -55,6 +56,7 @@ pub fn check_tokens(
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub);
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
@@ -108,8 +110,7 @@ pub fn check_tokens(
locator,
*start,
*end,
matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
autofix.into() && settings.rules.should_fix(&Rule::InvalidEscapeSequence),
));
}
}
@@ -148,7 +149,7 @@ pub fn check_tokens(
// COM812, COM818, COM819
if enforce_trailing_comma {
diagnostics.extend(
flake8_commas::rules::trailing_commas(tokens, settings, autofix)
flake8_commas::rules::trailing_commas(tokens, locator, settings, autofix)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
@@ -162,5 +163,10 @@ pub fn check_tokens(
);
}
// PYI033
if enforce_type_comment_in_stub && is_interface_definition {
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(tokens));
}
diagnostics
}

View File

@@ -29,6 +29,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
@@ -37,6 +39,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E225") => Rule::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E226") => Rule::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E227") => Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E228") => Rule::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
@@ -52,6 +66,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E273") => Rule::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E274") => Rule::TabBeforeKeyword,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E275") => Rule::MissingWhitespaceAfterKeyword,
(Pycodestyle, "E401") => Rule::MultipleImportsOnOneLine,
(Pycodestyle, "E402") => Rule::ModuleImportNotAtTopOfFile,
(Pycodestyle, "E501") => Rule::LineTooLong,
@@ -72,7 +88,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W191") => Rule::IndentationContainsTabs,
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,
@@ -141,9 +160,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
@@ -290,10 +311,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Simplify, "210") => Rule::IfExprWithTrueFalse,
(Flake8Simplify, "211") => Rule::IfExprWithFalseTrue,
(Flake8Simplify, "212") => Rule::IfExprWithTwistedArms,
(Flake8Simplify, "220") => Rule::AAndNotA,
(Flake8Simplify, "221") => Rule::AOrNotA,
(Flake8Simplify, "222") => Rule::OrTrue,
(Flake8Simplify, "223") => Rule::AndFalse,
(Flake8Simplify, "220") => Rule::ExprAndNotExpr,
(Flake8Simplify, "221") => Rule::ExprOrNotExpr,
(Flake8Simplify, "222") => Rule::ExprOrTrue,
(Flake8Simplify, "223") => Rule::ExprAndFalse,
(Flake8Simplify, "300") => Rule::YodaConditions,
(Flake8Simplify, "401") => Rule::DictGetWithDefault,
@@ -333,6 +354,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
// pydocstyle
(Pydocstyle, "100") => Rule::PublicModule,
@@ -481,8 +503,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// flake8-pyi
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "006") => Rule::BadVersionInfoComparison,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
@@ -516,6 +545,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::PreferListBuiltin,
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
@@ -604,13 +634,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "001") => Rule::AmbiguousUnicodeCharacterString,
(Ruff, "002") => Rule::AmbiguousUnicodeCharacterDocstring,
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "004") => Rule::KeywordArgumentBeforeStarArgument,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django
(Flake8Django, "001") => Rule::NullableModelStringField,
(Flake8Django, "003") => Rule::LocalsInRenderFunction,
(Flake8Django, "006") => Rule::ExcludeWithModelForm,
(Flake8Django, "007") => Rule::AllWithModelForm,
(Flake8Django, "008") => Rule::ModelWithoutDunderStr,
(Flake8Django, "013") => Rule::NonLeadingReceiverDecorator,

View File

@@ -6,7 +6,6 @@ use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use crate::registry::LintSource;
use crate::settings::Settings;
bitflags! {
@@ -21,7 +20,7 @@ impl Flags {
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Imports))
.any(|rule_code| rule_code.lint_source().is_imports())
{
Self::NOQA | Self::ISORT
} else {

View File

@@ -1,13 +0,0 @@
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
];
pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];

View File

@@ -1,70 +1,36 @@
//! Abstractions for Google-style docstrings.
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::sections::SectionKind;
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"Args",
"Arguments",
"Attention",
"Attributes",
"Caution",
"Danger",
"Error",
"Example",
"Examples",
"Hint",
"Important",
"Keyword Args",
"Keyword Arguments",
"Methods",
"Note",
"Notes",
"Return",
"Returns",
"Raises",
"References",
"See Also",
"Tip",
"Todo",
"Warning",
"Warnings",
"Warns",
"Yield",
"Yields",
])
});
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"args",
"arguments",
"attention",
"attributes",
"caution",
"danger",
"error",
"example",
"examples",
"hint",
"important",
"keyword args",
"keyword arguments",
"methods",
"note",
"notes",
"return",
"returns",
"raises",
"references",
"see also",
"tip",
"todo",
"warning",
"warnings",
"warns",
"yield",
"yields",
])
});
pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
SectionKind::Attributes,
SectionKind::Examples,
SectionKind::Methods,
SectionKind::Notes,
SectionKind::Raises,
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Yields,
// Google-only
SectionKind::Args,
SectionKind::Arguments,
SectionKind::Attention,
SectionKind::Caution,
SectionKind::Danger,
SectionKind::Error,
SectionKind::Example,
SectionKind::Hint,
SectionKind::Important,
SectionKind::KeywordArgs,
SectionKind::KeywordArguments,
SectionKind::Note,
SectionKind::Notes,
SectionKind::Return,
SectionKind::Tip,
SectionKind::Todo,
SectionKind::Warning,
SectionKind::Warnings,
SectionKind::Warns,
SectionKind::Yield,
];

View File

@@ -1,4 +1,3 @@
pub mod constants;
pub mod definition;
pub mod extraction;
pub mod google;

View File

@@ -1,40 +1,20 @@
//! Abstractions for NumPy-style docstrings.
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::sections::SectionKind;
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"short summary",
"extended summary",
"parameters",
"returns",
"yields",
"other parameters",
"raises",
"see also",
"notes",
"references",
"examples",
"attributes",
"methods",
])
});
pub(crate) static NUMPY_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"Short Summary",
"Extended Summary",
"Parameters",
"Returns",
"Yields",
"Other Parameters",
"Raises",
"See Also",
"Notes",
"References",
"Examples",
"Attributes",
"Methods",
])
});
pub(crate) static NUMPY_SECTIONS: &[SectionKind] = &[
SectionKind::Attributes,
SectionKind::Examples,
SectionKind::Methods,
SectionKind::Notes,
SectionKind::Raises,
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Yields,
// NumPy-only
SectionKind::ExtendedSummary,
SectionKind::OtherParameters,
SectionKind::Parameters,
SectionKind::ShortSummary,
];

View File

@@ -1,8 +1,126 @@
use strum_macros::EnumIter;
use crate::ast::whitespace;
use crate::docstrings::styles::SectionStyle;
#[derive(EnumIter, PartialEq, Eq, Debug, Clone, Copy)]
pub enum SectionKind {
Args,
Arguments,
Attention,
Attributes,
Caution,
Danger,
Error,
Example,
Examples,
ExtendedSummary,
Hint,
Important,
KeywordArgs,
KeywordArguments,
Methods,
Note,
Notes,
OtherParameters,
Parameters,
Raises,
References,
Return,
Returns,
SeeAlso,
ShortSummary,
Tip,
Todo,
Warning,
Warnings,
Warns,
Yield,
Yields,
}
impl SectionKind {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"args" => Some(Self::Args),
"arguments" => Some(Self::Arguments),
"attention" => Some(Self::Attention),
"attributes" => Some(Self::Attributes),
"caution" => Some(Self::Caution),
"danger" => Some(Self::Danger),
"error" => Some(Self::Error),
"example" => Some(Self::Example),
"examples" => Some(Self::Examples),
"extended summary" => Some(Self::ExtendedSummary),
"hint" => Some(Self::Hint),
"important" => Some(Self::Important),
"keyword args" => Some(Self::KeywordArgs),
"keyword arguments" => Some(Self::KeywordArguments),
"methods" => Some(Self::Methods),
"note" => Some(Self::Note),
"notes" => Some(Self::Notes),
"other parameters" => Some(Self::OtherParameters),
"parameters" => Some(Self::Parameters),
"raises" => Some(Self::Raises),
"references" => Some(Self::References),
"return" => Some(Self::Return),
"returns" => Some(Self::Returns),
"see also" => Some(Self::SeeAlso),
"short summary" => Some(Self::ShortSummary),
"tip" => Some(Self::Tip),
"todo" => Some(Self::Todo),
"warning" => Some(Self::Warning),
"warnings" => Some(Self::Warnings),
"warns" => Some(Self::Warns),
"yield" => Some(Self::Yield),
"yields" => Some(Self::Yields),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Args => "Args",
Self::Arguments => "Arguments",
Self::Attention => "Attention",
Self::Attributes => "Attributes",
Self::Caution => "Caution",
Self::Danger => "Danger",
Self::Error => "Error",
Self::Example => "Example",
Self::Examples => "Examples",
Self::ExtendedSummary => "Extended Summary",
Self::Hint => "Hint",
Self::Important => "Important",
Self::KeywordArgs => "Keyword Args",
Self::KeywordArguments => "Keyword Arguments",
Self::Methods => "Methods",
Self::Note => "Note",
Self::Notes => "Notes",
Self::OtherParameters => "Other Parameters",
Self::Parameters => "Parameters",
Self::Raises => "Raises",
Self::References => "References",
Self::Return => "Return",
Self::Returns => "Returns",
Self::SeeAlso => "See Also",
Self::ShortSummary => "Short Summary",
Self::Tip => "Tip",
Self::Todo => "Todo",
Self::Warning => "Warning",
Self::Warnings => "Warnings",
Self::Warns => "Warns",
Self::Yield => "Yield",
Self::Yields => "Yields",
}
}
}
#[derive(Debug)]
pub(crate) struct SectionContext<'a> {
/// The "kind" of the section, e.g. "SectionKind::Args" or "SectionKind::Returns".
pub(crate) kind: SectionKind,
/// The name of the section as it appears in the docstring, e.g. "Args" or "Returns".
pub(crate) section_name: &'a str,
pub(crate) previous_line: &'a str,
pub(crate) line: &'a str,
@@ -11,10 +129,13 @@ pub(crate) struct SectionContext<'a> {
pub(crate) original_index: usize,
}
fn suspected_as_section(line: &str, style: &SectionStyle) -> bool {
style
.lowercase_section_names()
.contains(&whitespace::leading_words(line).to_lowercase().as_str())
fn suspected_as_section(line: &str, style: &SectionStyle) -> Option<SectionKind> {
if let Some(kind) = SectionKind::from_str(whitespace::leading_words(line)) {
if style.sections().contains(&kind) {
return Some(kind);
}
}
None
}
/// Check if the suspected context is really a section header.
@@ -49,21 +170,15 @@ pub(crate) fn section_contexts<'a>(
lines: &'a [&'a str],
style: &SectionStyle,
) -> Vec<SectionContext<'a>> {
let suspected_section_indices: Vec<usize> = lines
let mut contexts = vec![];
for (kind, lineno) in lines
.iter()
.enumerate()
.filter_map(|(lineno, line)| {
if lineno > 0 && suspected_as_section(line, style) {
Some(lineno)
} else {
None
}
})
.collect();
let mut contexts = vec![];
for lineno in suspected_section_indices {
.skip(1)
.filter_map(|(lineno, line)| suspected_as_section(line, style).map(|kind| (kind, lineno)))
{
let context = SectionContext {
kind,
section_name: whitespace::leading_words(lines[lineno]),
previous_line: lines[lineno - 1],
line: lines[lineno],
@@ -76,11 +191,12 @@ pub(crate) fn section_contexts<'a>(
}
}
let mut truncated_contexts = vec![];
let mut truncated_contexts = Vec::with_capacity(contexts.len());
let mut end: Option<usize> = None;
for context in contexts.into_iter().rev() {
let next_end = context.original_index;
truncated_contexts.push(SectionContext {
kind: context.kind,
section_name: context.section_name,
previous_line: context.previous_line,
line: context.line,

View File

@@ -1,8 +1,6 @@
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_NAMES};
use crate::docstrings::numpy::{LOWERCASE_NUMPY_SECTION_NAMES, NUMPY_SECTION_NAMES};
use crate::docstrings::google::GOOGLE_SECTIONS;
use crate::docstrings::numpy::NUMPY_SECTIONS;
use crate::docstrings::sections::SectionKind;
pub(crate) enum SectionStyle {
Numpy,
@@ -10,17 +8,10 @@ pub(crate) enum SectionStyle {
}
impl SectionStyle {
pub(crate) fn section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
pub(crate) fn sections(&self) -> &[SectionKind] {
match self {
SectionStyle::Numpy => &NUMPY_SECTION_NAMES,
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
}
}
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
match self {
SectionStyle::Numpy => &LOWERCASE_NUMPY_SECTION_NAMES,
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
SectionStyle::Numpy => NUMPY_SECTIONS,
SectionStyle::Google => GOOGLE_SECTIONS,
}
}
}

View File

@@ -576,6 +576,8 @@ mod tests {
let expected = Pyproject::new(Options {
pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
}),
..default_options([Linter::Pydocstyle.into()])
});

View File

@@ -1,13 +1,12 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::Rule;
use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet};
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -25,11 +24,7 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
path: &Path,
pattern_code_pairs: &'a [(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)],
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
) -> FxHashSet<&'a Rule> {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs
@@ -39,8 +34,8 @@ pub(crate) fn ignores_from_path<'a>(
debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
basename.deref().glob().regex(),
&**codes
basename.glob().regex(),
codes
);
return Some(codes.iter());
}
@@ -48,8 +43,8 @@ pub(crate) fn ignores_from_path<'a>(
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.deref().glob().regex(),
&**codes
absolute.glob().regex(),
codes
);
return Some(codes.iter());
}

View File

@@ -13,7 +13,6 @@ pub use violation::{AutofixKind, Availability as AutofixAvailability};
mod ast;
mod autofix;
pub mod cache;
mod checkers;
mod codes;
mod cst;
@@ -33,10 +32,8 @@ pub mod resolver;
mod rule_redirects;
mod rule_selector;
mod rules;
mod rustpython_helpers;
pub mod settings;
pub mod source_code;
mod vendor;
mod violation;
mod visibility;

View File

@@ -14,7 +14,6 @@ use crate::rules::{
flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
};
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::options::Options;
use crate::settings::{defaults, flags, Settings};
@@ -175,7 +174,7 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
Settings::from_configuration(configuration, Path::new(".")).map_err(|e| e.to_string())?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
// Map row and column locations to byte slices (lazily).
let locator = Locator::new(contents);
@@ -202,8 +201,8 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
&indexer,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
flags::Autofix::Enabled,
);
let messages: Vec<ExpandedMessage> = diagnostics

View File

@@ -20,11 +20,12 @@ use crate::directives::Directives;
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
use crate::message::{Message, Source};
use crate::noqa::{add_noqa, rule_is_ignored};
use crate::registry::{Diagnostic, LintSource, Rule};
use crate::registry::{Diagnostic, Rule};
use crate::resolver::is_interface_definition_path;
use crate::rules::pycodestyle;
use crate::settings::{flags, Settings};
use crate::source_code::{Indexer, Locator, Stylist};
use crate::{directives, fs, rustpython_helpers};
use crate::{directives, fs};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
@@ -62,8 +63,8 @@ pub fn check_path(
indexer: &Indexer,
directives: &Directives,
settings: &Settings,
autofix: flags::Autofix,
noqa: flags::Noqa,
autofix: flags::Autofix,
) -> LinterResult<Vec<Diagnostic>> {
// Aggregate all diagnostics.
let mut diagnostics = vec![];
@@ -81,16 +82,23 @@ pub fn check_path(
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Tokens))
.any(|rule_code| rule_code.lint_source().is_tokens())
{
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix));
let is_interface_definition = is_interface_definition_path(path);
diagnostics.extend(check_tokens(
locator,
&tokens,
settings,
autofix,
is_interface_definition,
));
}
// Run the filesystem-based rules.
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Filesystem))
.any(|rule_code| rule_code.lint_source().is_filesystem())
{
diagnostics.extend(check_file_path(path, package, settings));
}
@@ -99,23 +107,29 @@ pub fn check_path(
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::LogicalLines))
.any(|rule_code| rule_code.lint_source().is_logical_lines())
{
diagnostics.extend(check_logical_lines(&tokens, locator, stylist, settings));
diagnostics.extend(check_logical_lines(
&tokens,
locator,
stylist,
settings,
flags::Autofix::Enabled,
));
}
// Run the AST-based rules.
let use_ast = settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Ast));
.any(|rule_code| rule_code.lint_source().is_ast());
let use_imports = !directives.isort.skip_file
&& settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Imports));
.any(|rule_code| rule_code.lint_source().is_imports());
if use_ast || use_imports || use_doc_lines {
match rustpython_helpers::parse_program_tokens(tokens, &path.to_string_lossy()) {
match ruff_rustpython::parse_program_tokens(tokens, &path.to_string_lossy()) {
Ok(python_ast) => {
if use_ast {
diagnostics.extend(check_ast(
@@ -177,7 +191,7 @@ pub fn check_path(
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::PhysicalLines))
.any(|rule_code| rule_code.lint_source().is_physical_lines())
{
diagnostics.extend(check_physical_lines(
path,
@@ -199,11 +213,11 @@ pub fn check_path(
};
// Enforce `noqa` directives.
if (matches!(noqa, flags::Noqa::Enabled) && !diagnostics.is_empty())
if (noqa.into() && !diagnostics.is_empty())
|| settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::NoQa))
.any(|rule_code| rule_code.lint_source().is_noqa())
{
check_noqa(
&mut diagnostics,
@@ -226,7 +240,7 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
let contents = std::fs::read_to_string(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(&contents);
// Map row and column locations to byte slices (lazily).
let locator = Locator::new(&contents);
@@ -255,8 +269,8 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
&indexer,
&directives,
settings,
flags::Autofix::Disabled,
flags::Noqa::Disabled,
flags::Autofix::Disabled,
);
// Log any parse errors.
@@ -287,10 +301,11 @@ pub fn lint_only(
path: &Path,
package: Option<&Path>,
settings: &Settings,
noqa: flags::Noqa,
autofix: flags::Autofix,
) -> LinterResult<Vec<Message>> {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(contents);
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
// Map row and column locations to byte slices (lazily).
let locator = Locator::new(contents);
@@ -316,8 +331,8 @@ pub fn lint_only(
&indexer,
&directives,
settings,
noqa,
autofix,
flags::Noqa::Enabled,
);
// Convert from diagnostics to messages.
@@ -331,7 +346,9 @@ pub fn lint_only(
} else {
None
};
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source)
let lineno = diagnostic.location.row();
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source, noqa_row)
})
.collect()
})
@@ -343,6 +360,7 @@ pub fn lint_fix<'a>(
contents: &'a str,
path: &Path,
package: Option<&Path>,
noqa: flags::Noqa,
settings: &Settings,
) -> Result<(LinterResult<Vec<Message>>, Cow<'a, str>, FixTable)> {
let mut transformed = Cow::Borrowed(contents);
@@ -359,7 +377,7 @@ pub fn lint_fix<'a>(
// Continuously autofix until the source code stabilizes.
loop {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&transformed);
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(&transformed);
// Map row and column locations to byte slices (lazily).
let locator = Locator::new(&transformed);
@@ -385,8 +403,8 @@ pub fn lint_fix<'a>(
&indexer,
&directives,
settings,
noqa,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
);
if iterations == 0 {
@@ -469,7 +487,14 @@ This indicates a bug in `{}`. If you could open an issue at:
} else {
None
};
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source)
let lineno = diagnostic.location.row();
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
Message::from_diagnostic(
diagnostic,
path_lossy.to_string(),
source,
noqa_row,
)
})
.collect()
}),

View File

@@ -16,6 +16,7 @@ pub struct Message {
pub fix: Option<Fix>,
pub filename: String,
pub source: Option<Source>,
pub noqa_row: usize,
}
impl Message {
@@ -23,6 +24,7 @@ impl Message {
diagnostic: Diagnostic,
filename: String,
source: Option<Source>,
noqa_row: usize,
) -> Self {
Self {
kind: diagnostic.kind,
@@ -34,6 +36,7 @@ impl Message {
fix: diagnostic.fix,
filename,
source,
noqa_row,
}
}
}

View File

@@ -53,10 +53,26 @@ ruff_macros::register_rules!(
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -77,7 +93,10 @@ ruff_macros::register_rules!(
rules::pycodestyle::rules::IOError,
rules::pycodestyle::rules::SyntaxError,
// pycodestyle warnings
rules::pycodestyle::rules::IndentationContainsTabs,
rules::pycodestyle::rules::TrailingWhitespace,
rules::pycodestyle::rules::NoNewLineAtEndOfFile,
rules::pycodestyle::rules::BlankLineContainsWhitespace,
rules::pycodestyle::rules::DocLineTooLong,
rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
@@ -131,6 +150,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::BadStrStripCall,
rules::pylint::rules::CollapsibleElseIf,
rules::pylint::rules::UselessImportAlias,
rules::pylint::rules::UnnecessaryDirectLambdaCall,
rules::pylint::rules::NonlocalWithoutBinding,
@@ -144,6 +164,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::ConsiderUsingSysExit,
rules::pylint::rules::MagicValueComparison,
rules::pylint::rules::UselessElseOnLoop,
rules::pylint::rules::GlobalStatement,
rules::pylint::rules::GlobalVariableNotAssigned,
rules::pylint::rules::TooManyReturnStatements,
rules::pylint::rules::TooManyArguments,
@@ -278,10 +299,10 @@ ruff_macros::register_rules!(
rules::flake8_simplify::rules::IfExprWithTrueFalse,
rules::flake8_simplify::rules::IfExprWithFalseTrue,
rules::flake8_simplify::rules::IfExprWithTwistedArms,
rules::flake8_simplify::rules::AAndNotA,
rules::flake8_simplify::rules::AOrNotA,
rules::flake8_simplify::rules::OrTrue,
rules::flake8_simplify::rules::AndFalse,
rules::flake8_simplify::rules::ExprAndNotExpr,
rules::flake8_simplify::rules::ExprOrNotExpr,
rules::flake8_simplify::rules::ExprOrTrue,
rules::flake8_simplify::rules::ExprAndFalse,
rules::flake8_simplify::rules::YodaConditions,
rules::flake8_simplify::rules::DictGetWithDefault,
// pyupgrade
@@ -320,6 +341,7 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
// pydocstyle
rules::pydocstyle::rules::PublicModule,
rules::pydocstyle::rules::PublicClass,
@@ -455,8 +477,15 @@ ruff_macros::register_rules!(
rules::flake8_errmsg::rules::DotFormatInException,
// flake8-pyi
rules::flake8_pyi::rules::PrefixTypeParams,
rules::flake8_pyi::rules::BadVersionInfoComparison,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
rules::flake8_pyi::rules::TypeCommentInStub,
// flake8-pytest-style
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
rules::flake8_pytest_style::rules::FixturePositionalArgs,
@@ -491,6 +520,7 @@ ruff_macros::register_rules!(
rules::flake8_pie::rules::UnnecessaryDictKwargs,
rules::flake8_pie::rules::PreferListBuiltin,
rules::flake8_pie::rules::SingleStartsEndsWith,
rules::flake8_pie::rules::UnnecessaryComprehensionAnyAll,
// flake8-commas
rules::flake8_commas::rules::TrailingCommaMissing,
rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
@@ -565,12 +595,14 @@ ruff_macros::register_rules!(
rules::ruff::rules::AmbiguousUnicodeCharacterString,
rules::ruff::rules::AmbiguousUnicodeCharacterDocstring,
rules::ruff::rules::AmbiguousUnicodeCharacterComment,
rules::ruff::rules::KeywordArgumentBeforeStarArgument,
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
// flake8-django
rules::flake8_django::rules::NullableModelStringField,
rules::flake8_django::rules::LocalsInRenderFunction,
rules::flake8_django::rules::ExcludeWithModelForm,
rules::flake8_django::rules::AllWithModelForm,
rules::flake8_django::rules::ModelWithoutDunderStr,
rules::flake8_django::rules::NonLeadingReceiverDecorator,
);
@@ -756,6 +788,7 @@ impl Linter {
}
}
#[derive(is_macro::Is)]
pub enum LintSource {
Ast,
Io,
@@ -763,7 +796,7 @@ pub enum LintSource {
LogicalLines,
Tokens,
Imports,
NoQa,
Noqa,
Filesystem,
}
@@ -772,7 +805,7 @@ impl Rule {
/// physical lines).
pub const fn lint_source(&self) -> &'static LintSource {
match self {
Rule::UnusedNOQA => &LintSource::NoQa,
Rule::UnusedNOQA => &LintSource::Noqa,
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::DocLineTooLong
@@ -785,7 +818,10 @@ impl Rule {
| Rule::ShebangNewline
| Rule::BidirectionalUnicode
| Rule::ShebangPython
| Rule::ShebangWhitespace => &LintSource::PhysicalLines,
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -803,13 +839,20 @@ impl Rule {
| Rule::MultipleStatementsOnOneLineColon
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
| Rule::TrailingCommaProhibited
| Rule::TypeCommentInStub => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MissingWhitespaceAfterKeyword
| Rule::MissingWhitespaceAroundArithmeticOperator
| Rule::MissingWhitespaceAroundBitwiseOrShiftOperator
| Rule::MissingWhitespaceAroundModuloOperator
| Rule::MissingWhitespaceAroundOperator
| Rule::MissingWhitespaceAroundParameterEquals
| Rule::MultipleLeadingHashesForBlockComment
| Rule::MultipleSpacesAfterKeyword
| Rule::MultipleSpacesAfterOperator
@@ -827,8 +870,10 @@ impl Rule {
| Rule::TooFewSpacesBeforeInlineComment
| Rule::UnexpectedIndentation
| Rule::UnexpectedIndentationComment
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
| Rule::WhitespaceAfterOpenBracket
| Rule::WhitespaceBeforeCloseBracket
| Rule::WhitespaceBeforeParameters
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
_ => &LintSource::Ast,
}

View File

@@ -1,4 +1,4 @@
//! Discover Python files, and their corresponding `Settings`, from the
//! Discover Python files, and their corresponding [`Settings`], from the
//! filesystem.
use std::collections::BTreeMap;
@@ -19,7 +19,7 @@ use crate::settings::{pyproject, AllSettings, Settings};
/// The strategy used to discover the relevant `pyproject.toml` file for each
/// Python file.
#[derive(Debug)]
#[derive(Debug, is_macro::Is)]
pub enum PyprojectDiscovery {
/// Use a fixed `pyproject.toml` file for all Python files (i.e., one
/// provided on the command-line).
@@ -30,7 +30,7 @@ pub enum PyprojectDiscovery {
}
impl PyprojectDiscovery {
fn top_level_settings(&self) -> &AllSettings {
pub fn top_level_settings(&self) -> &AllSettings {
match self {
PyprojectDiscovery::Fixed(settings) => settings,
PyprojectDiscovery::Hierarchical(settings) => settings,
@@ -65,12 +65,12 @@ pub struct Resolver {
}
impl Resolver {
/// Add a resolved `Settings` under a given `PathBuf` scope.
/// Add a resolved [`Settings`] under a given [`PathBuf`] scope.
pub fn add(&mut self, path: PathBuf, settings: AllSettings) {
self.settings.insert(path, settings);
}
/// Return the appropriate `AllSettings` for a given `Path`.
/// Return the appropriate [`AllSettings`] for a given [`Path`].
pub fn resolve_all<'a>(
&'a self,
path: &Path,
@@ -82,13 +82,7 @@ impl Resolver {
.settings
.iter()
.rev()
.find_map(|(root, settings)| {
if path.starts_with(root) {
Some(settings)
} else {
None
}
})
.find_map(|(root, settings)| path.starts_with(root).then_some(settings))
.unwrap_or(default),
}
}
@@ -97,7 +91,7 @@ impl Resolver {
&self.resolve_all(path, strategy).lib
}
/// Return an iterator over the resolved `Settings` in this `Resolver`.
/// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
pub fn iter(&self) -> impl Iterator<Item = &AllSettings> {
self.settings.values()
}
@@ -112,8 +106,8 @@ impl ConfigProcessor for &NoOpProcessor {
fn process_config(&self, _config: &mut Configuration) {}
}
/// Recursively resolve a `Configuration` from a `pyproject.toml` file at the
/// specified `Path`.
/// Recursively resolve a [`Configuration`] from a `pyproject.toml` file at the
/// specified [`Path`].
// TODO(charlie): This whole system could do with some caching. Right now, if a
// configuration file extends another in the same path, we'll re-parse the same
// file at least twice (possibly more than twice, since we'll also parse it when
@@ -161,26 +155,26 @@ pub fn resolve_configuration(
Ok(configuration)
}
/// Extract the project root (scope) and `Settings` from a given
/// Extract the project root (scope) and [`Settings`] from a given
/// `pyproject.toml`.
pub fn resolve_scoped_settings(
pyproject: &Path,
relativity: &Relativity,
processor: impl ConfigProcessor,
) -> Result<(PathBuf, AllSettings)> {
let project_root = relativity.resolve(pyproject);
let configuration = resolve_configuration(pyproject, relativity, processor)?;
let project_root = relativity.resolve(pyproject);
let settings = AllSettings::from_configuration(configuration, &project_root)?;
Ok((project_root, settings))
}
/// Extract the `Settings` from a given `pyproject.toml`.
/// Extract the [`Settings`] from a given `pyproject.toml`.
pub fn resolve_settings(pyproject: &Path, relativity: &Relativity) -> Result<AllSettings> {
let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, &NoOpProcessor)?;
Ok(settings)
}
/// Extract the `Settings` from a given `pyproject.toml` and process the
/// Extract the [`Settings`] from a given `pyproject.toml` and process the
/// configuration with the given [`ConfigProcessor`].
pub fn resolve_settings_with_processor(
pyproject: &Path,
@@ -197,18 +191,18 @@ fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::Gl
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
}
/// Return `true` if the `Path` appears to be that of a Python file.
/// Return `true` if the [`Path`] appears to be that of a Python file.
fn is_python_path(path: &Path) -> bool {
path.extension()
.map_or(false, |ext| ext == "py" || ext == "pyi")
}
/// Return `true` if the `Path` appears to be that of a Python interface definition file (`.pyi`).
/// Return `true` if the [`Path`] appears to be that of a Python interface definition file (`.pyi`).
pub fn is_interface_definition_path(path: &Path) -> bool {
path.extension().map_or(false, |ext| ext == "pyi")
}
/// Return `true` if the `Entry` appears to be that of a Python file.
/// Return `true` if the [`DirEntry`] appears to be that of a Python file.
pub fn is_python_entry(entry: &DirEntry) -> bool {
is_python_path(entry.path())
&& !entry
@@ -228,7 +222,7 @@ pub fn python_files_in_path(
// Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default();
let mut seen = FxHashSet::default();
if matches!(pyproject_strategy, PyprojectDiscovery::Hierarchical(..)) {
if pyproject_strategy.is_hierarchical() {
for path in &paths {
for ancestor in path.ancestors() {
if seen.insert(ancestor) {
@@ -277,7 +271,7 @@ pub fn python_files_in_path(
Box::new(|result| {
// Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents.
if matches!(pyproject_strategy, PyprojectDiscovery::Hierarchical(..)) {
if pyproject_strategy.is_hierarchical() {
if let Ok(entry) = &result {
if entry
.file_type()
@@ -357,7 +351,7 @@ pub fn python_files_in_path(
Ok((files.into_inner().unwrap(), resolver.into_inner().unwrap()))
}
/// Return `true` if the Python file at `Path` is _not_ excluded.
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
pub fn python_file_at_path(
path: &Path,
pyproject_strategy: &PyprojectDiscovery,
@@ -372,7 +366,7 @@ pub fn python_file_at_path(
// Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default();
if matches!(pyproject_strategy, PyprojectDiscovery::Hierarchical(..)) {
if pyproject_strategy.is_hierarchical() {
for ancestor in path.ancestors() {
if let Some(pyproject) = settings_toml(ancestor)? {
let (root, settings) =
@@ -386,7 +380,7 @@ pub fn python_file_at_path(
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
}
/// Return `true` if the given top-level `Path` should be excluded.
/// Return `true` if the given top-level [`Path`] should be excluded.
fn is_file_excluded(
path: &Path,
resolver: &Resolver,

View File

@@ -92,5 +92,7 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// TODO(charlie): Remove by 2023-04-01.
("TYP", "TCH"),
("TYP001", "TCH001"),
// TODO(charlie): Remove by 2023-06-01.
("RUF004", "B026"),
])
});

View File

@@ -60,9 +60,7 @@ pub fn commented_out_code(
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::CommentedOutCode)
{
if autofix.into() && settings.rules.should_fix(&Rule::CommentedOutCode) {
diagnostic.amend(Fix::deletion(location, end_location));
}
Some(diagnostic)

View File

@@ -398,9 +398,9 @@ define_violation!(
/// ```
///
/// ## 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)
/// - [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,
}

View File

@@ -1,5 +1,6 @@
//! Settings for the `flake-annotations` plugin.
use ruff_macros::CacheKey;
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -37,8 +38,8 @@ pub struct Options {
/// Whether to suppress `ANN200`-level violations for functions that meet
/// either of the following criteria:
///
/// * Contain no `return` statement.
/// * Explicit `return` statement(s) all return `None` (explicitly or
/// - Contain no `return` statement.
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
pub suppress_none_returning: Option<bool>,
#[option(
@@ -60,7 +61,7 @@ pub struct Options {
pub ignore_fully_untyped: Option<bool>,
}
#[derive(Debug, Default, Hash)]
#[derive(Debug, Default, CacheKey)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,

View File

@@ -1,10 +1,12 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::{Constant, Expr, ExprKind};
use crate::checkers::ast::Checker;
const PASSWORD_NAMES: [&str; 7] = [
"password", "pass", "passwd", "pwd", "secret", "token", "secrete",
];
static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(^|_)(?i)(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)($|_)").unwrap()
});
pub fn string_literal(expr: &Expr) -> Option<&str> {
match &expr.node {
@@ -16,11 +18,8 @@ pub fn string_literal(expr: &Expr) -> Option<&str> {
}
}
// Maybe use regex for this?
pub fn matches_password_name(string: &str) -> bool {
PASSWORD_NAMES
.iter()
.any(|name| string.to_lowercase().contains(name))
PASSWORD_CANDIDATE_REGEX.is_match(string)
}
pub fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {

View File

@@ -1,5 +1,6 @@
use rustpython_parser::ast::Stmt;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Located, StmtKind};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
@@ -16,7 +17,7 @@ impl Violation for Assert {
}
/// S101
pub fn assert_used(stmt: &Located<StmtKind>) -> Diagnostic {
pub fn assert_used(stmt: &Stmt) -> Diagnostic {
Diagnostic::new(
Assert,
Range::new(stmt.location, stmt.location.with_col_offset("assert".len())),

View File

@@ -1,11 +1,13 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{ArgData, Arguments, Expr, Located};
use rustpython_parser::ast::{Arg, Arguments, Expr};
use ruff_macros::{define_violation, derive_message_formats};
use super::super::helpers::{matches_password_name, string_literal};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use super::super::helpers::{matches_password_name, string_literal};
define_violation!(
pub struct HardcodedPasswordDefault {
pub string: String,
@@ -19,7 +21,7 @@ impl Violation for HardcodedPasswordDefault {
}
}
fn check_password_kwarg(arg: &Located<ArgData>, default: &Expr) -> Option<Diagnostic> {
fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
let string = string_literal(default).filter(|string| !string.is_empty())?;
let kwarg_name = &arg.node.arg;
if !matches_password_name(kwarg_name) {

View File

@@ -33,8 +33,8 @@ define_violation!(
/// ```
///
/// ## 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)
/// - [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;
);
impl Violation for HardcodedSQLExpression {

View File

@@ -1,6 +1,6 @@
//! Settings for the `flake8-bandit` plugin.
use ruff_macros::ConfigurationOptions;
use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -45,7 +45,7 @@ pub struct Options {
pub check_typed_exception: Option<bool>,
}
#[derive(Debug, Hash)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub hardcoded_tmp_directory: Vec<String>,
pub check_typed_exception: bool,

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_bandit/mod.rs
source: crates/ruff/src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
@@ -105,46 +105,46 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 23
column: 16
end_location:
row: 23
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 24
column: 12
end_location:
row: 24
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 25
column: 14
end_location:
row: 25
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 26
row: 22
column: 11
end_location:
row: 26
row: 22
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 23
column: 11
end_location:
row: 23
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 25
column: 16
end_location:
row: 25
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 26
column: 12
end_location:
row: 26
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
@@ -161,9 +161,31 @@ expression: diagnostics
string: s3cr3t
location:
row: 28
column: 13
column: 11
end_location:
row: 28
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 29
column: 14
end_location:
row: 29
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 30
column: 13
end_location:
row: 30
column: 21
fix: ~
parent: ~
@@ -171,10 +193,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 29
row: 31
column: 15
end_location:
row: 29
row: 31
column: 23
fix: ~
parent: ~
@@ -182,10 +204,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 30
row: 32
column: 23
end_location:
row: 30
row: 32
column: 31
fix: ~
parent: ~
@@ -193,10 +215,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 31
row: 33
column: 23
end_location:
row: 31
row: 33
column: 31
fix: ~
parent: ~
@@ -204,10 +226,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 35
row: 37
column: 15
end_location:
row: 35
row: 37
column: 23
fix: ~
parent: ~
@@ -215,10 +237,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 39
row: 41
column: 19
end_location:
row: 39
row: 41
column: 27
fix: ~
parent: ~
@@ -226,10 +248,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 40
row: 42
column: 16
end_location:
row: 40
row: 42
column: 24
fix: ~
parent: ~
@@ -237,10 +259,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 41
row: 43
column: 17
end_location:
row: 41
row: 43
column: 25
fix: ~
parent: ~
@@ -248,10 +270,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 42
row: 44
column: 14
end_location:
row: 42
row: 44
column: 22
fix: ~
parent: ~
@@ -259,10 +281,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 43
row: 45
column: 17
end_location:
row: 43
row: 45
column: 25
fix: ~
parent: ~
@@ -270,10 +292,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 44
row: 46
column: 16
end_location:
row: 44
row: 46
column: 24
fix: ~
parent: ~
@@ -281,10 +303,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 45
row: 47
column: 18
end_location:
row: 45
row: 47
column: 26
fix: ~
parent: ~
@@ -292,10 +314,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 47
row: 49
column: 12
end_location:
row: 47
row: 49
column: 20
fix: ~
parent: ~
@@ -303,10 +325,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 48
row: 50
column: 9
end_location:
row: 48
row: 50
column: 17
fix: ~
parent: ~
@@ -314,10 +336,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 49
row: 51
column: 10
end_location:
row: 49
row: 51
column: 18
fix: ~
parent: ~
@@ -325,10 +347,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 50
row: 52
column: 7
end_location:
row: 50
row: 52
column: 15
fix: ~
parent: ~
@@ -336,10 +358,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 51
row: 53
column: 10
end_location:
row: 51
row: 53
column: 18
fix: ~
parent: ~
@@ -347,10 +369,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 52
row: 54
column: 9
end_location:
row: 52
row: 54
column: 17
fix: ~
parent: ~
@@ -358,10 +380,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 53
row: 55
column: 11
end_location:
row: 53
row: 55
column: 19
fix: ~
parent: ~
@@ -369,10 +391,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 54
row: 56
column: 20
end_location:
row: 54
row: 56
column: 28
fix: ~
parent: ~
@@ -380,10 +402,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "1\n2"
location:
row: 56
row: 58
column: 12
end_location:
row: 56
row: 58
column: 18
fix: ~
parent: ~
@@ -391,10 +413,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "3\t4"
location:
row: 59
row: 61
column: 12
end_location:
row: 59
row: 61
column: 18
fix: ~
parent: ~
@@ -402,10 +424,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "5\r6"
location:
row: 62
row: 64
column: 12
end_location:
row: 62
row: 64
column: 18
fix: ~
parent: ~

View File

@@ -56,6 +56,9 @@ const FUNC_CALL_NAME_ALLOWLIST: &[&str] = &[
"bytes",
"int",
"float",
"getint",
"getfloat",
"getboolean",
];
const FUNC_DEF_NAME_ALLOWLIST: &[&str] = &["__setitem__"];

View File

@@ -85,10 +85,10 @@ expression: diagnostics
- kind:
BooleanPositionalArgInFunctionDefinition: ~
location:
row: 77
row: 81
column: 18
end_location:
row: 77
row: 81
column: 29
fix: ~
parent: ~

View File

@@ -1,10 +1,10 @@
use ruff_macros::{define_violation, derive_message_formats};
use ruff_python::string::is_lower;
use rustpython_parser::ast::{ExprKind, Stmt, StmtKind};
use rustpython_parser::ast::{ExprKind, Stmt};
use crate::ast::types::Range;
use ruff_macros::{define_violation, derive_message_formats};
use ruff_python::str::is_lower;
use crate::ast::helpers::RaiseStatementVisitor;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
@@ -16,62 +16,32 @@ impl Violation for RaiseWithoutFromInsideExcept {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Within an except clause, raise exceptions with `raise ... from err` or `raise ... \
"Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... \
from None` to distinguish them from errors in exception handling"
)
}
}
struct RaiseVisitor {
diagnostics: Vec<Diagnostic>,
}
/// B904
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let raises = {
let mut visitor = RaiseStatementVisitor::default();
visitor::walk_body(&mut visitor, body);
visitor.raises
};
impl<'a> Visitor<'a> for RaiseVisitor {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Raise {
exc: Some(exc),
cause: None,
} => match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
self.diagnostics.push(Diagnostic::new(
RaiseWithoutFromInsideExcept,
Range::from_located(stmt),
));
}
},
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::If { body, orelse, .. } => {
visitor::walk_body(self, body);
visitor::walk_body(self, orelse);
}
StmtKind::While { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. }
| StmtKind::For { body, .. }
| StmtKind::AsyncFor { body, .. } => {
visitor::walk_body(self, body);
}
StmtKind::Match { cases, .. } => {
for case in cases {
visitor::walk_body(self, &case.body);
for (range, exc, cause) in raises {
if cause.is_none() {
if let Some(exc) = exc {
match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
checker
.diagnostics
.push(Diagnostic::new(RaiseWithoutFromInsideExcept, range));
}
}
}
_ => {}
}
}
}
/// B904
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let mut visitor = RaiseVisitor {
diagnostics: vec![],
};
visitor::walk_body(&mut visitor, body);
checker.diagnostics.extend(visitor.diagnostics);
}

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