Compare commits

...

70 Commits

Author SHA1 Message Date
Charlie Marsh
f8d46d09ef Implement asyncio-dangling-task to track asyncio.create_task calls (#2935)
This rule guards against `asyncio.create_task` usages of the form:

```py
asyncio.create_task(coordinator.ws_connect())  # Error
```

...which can lead to unexpected bugs due to the lack of a strong reference to the created task. See Will McGugan's blog post for reference: https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/.

Note that we can't detect issues like:

```py
def f():
    # Stored as `task`, but never used...
    task = asyncio.create_task(coordinator.ws_connect())
```

So that would be a false negative. But this catches the common case of failing to assign the task in any way.

Closes #2809.
2023-02-15 15:19:03 -05:00
Martin Fischer
294cd95c54 Update clap to fix ruff check --help description
My two clap bug fixes[1][2] have been merged and released
(see the change in README.md).

[1]: https://github.com/clap-rs/clap/pull/4710
[2]: https://github.com/clap-rs/clap/pull/4712
2023-02-15 13:30:06 -05:00
Charlie Marsh
d8e709648d Add Transformers to README (#2933) 2023-02-15 17:09:50 +00:00
Charlie Marsh
52cc4d6537 Deduplicate files provided on the command-line (#2931) 2023-02-15 12:08:34 -05:00
messense
08e9d12137 Upload ruff binaries to GitHub release (#2930) 2023-02-15 12:07:47 -05:00
Charlie Marsh
39fdc71b49 Bump version to 0.0.247 (#2932) 2023-02-15 12:06:58 -05:00
Charlie Marsh
6b0736cf4b Allow private accesses on current class (#2929) 2023-02-15 16:52:05 +00:00
Charlie Marsh
58269a918a Apply nullable-model-string-field to all classes (#2928) 2023-02-15 15:54:14 +00:00
Sawbez
9168a12679 [docs] flake8-self Private member access docs (#2912) 2023-02-15 15:42:38 +00:00
Charlie Marsh
cb971d3a48 Respect self as positional-only argument in annotation rules (#2927) 2023-02-15 15:25:17 +00:00
Charlie Marsh
57a5071b4e Rename some methods on Locator (#2926) 2023-02-15 10:21:49 -05:00
Charlie Marsh
976fe364d4 Remove setup.py (#2922) 2023-02-15 14:35:11 +00:00
messense
028c7855b2 Simplify release workflows (#2913)
* No need to build for PyPy since it only contains a binary so the platform tag is the same as CPython
* Update `maturin-action` location
2023-02-15 08:15:19 -05:00
Nick Pope
e5179f67fd Remove autogenerated docs/rules/*.md files (#2917) 2023-02-15 08:11:11 -05:00
Simon Brugman
c9c199dbca Remove testing resource introduced in #2891 (#2916) 2023-02-15 08:10:58 -05:00
Martin Fischer
70e378b736 Implement shell autocompletion for rule codes
For example:

    $ ruff check --select=EM<Tab>
    EM          -- flake8-errmsg
    EM10   EM1  --
    EM101       -- raw-string-in-exception
    EM102       -- f-string-in-exception
    EM103       -- dot-format-in-exception

(You will need to enable autocompletion as described
 in the Autocompletion section in the README.)

Fixes #2808.

(The --help help change in the README is due to a clap bug,
 for which I already submitted a fix:
 https://github.com/clap-rs/clap/pull/4710.)
2023-02-15 08:09:34 -05:00
Charlie Marsh
ca49b00e55 Add initial formatter implementation (#2883)
# Summary

This PR contains the code for the autoformatter proof-of-concept.

## Crate structure

The primary formatting hook is the `fmt` function in `crates/ruff_python_formatter/src/lib.rs`.

The current formatter approach is outlined in `crates/ruff_python_formatter/src/lib.rs`, and is structured as follows:

- Tokenize the code using the RustPython lexer.
- In `crates/ruff_python_formatter/src/trivia.rs`, extract a variety of trivia tokens from the token stream. These include comments, trailing commas, and empty lines.
- Generate the AST via the RustPython parser.
- In `crates/ruff_python_formatter/src/cst.rs`, convert the AST to a CST structure. As of now, the CST is nearly identical to the AST, except that every node gets a `trivia` vector. But we might want to modify it further.
- In `crates/ruff_python_formatter/src/attachment.rs`, attach each trivia token to the corresponding CST node. The logic for this is mostly in `decorate_trivia` and is ported almost directly from Prettier (given each token, find its preceding, following, and enclosing nodes, then attach the token to the appropriate node in a second pass).
- In `crates/ruff_python_formatter/src/newlines.rs`, normalize newlines to match Black’s preferences. This involves traversing the CST and inserting or removing `TriviaToken` values as we go.
- Call `format!` on the CST, which delegates to type-specific formatter implementations (e.g., `crates/ruff_python_formatter/src/format/stmt.rs` for `Stmt` nodes, and similar for `Expr` nodes; the others are trivial). Those type-specific implementations delegate to kind-specific functions (e.g., `format_func_def`).

## Testing and iteration

The formatter is being developed against the Black test suite, which was copied over in-full to `crates/ruff_python_formatter/resources/test/fixtures/black`.

The Black fixtures had to be modified to create `[insta](https://github.com/mitsuhiko/insta)`-compatible snapshots, which now exist in the repo.

My approach thus far has been to try and improve coverage by tackling fixtures one-by-one.

## What works, and what doesn’t

- *Most* nodes are supported at a basic level (though there are a few stragglers at time of writing, like `StmtKind::Try`).
- Newlines are properly preserved in most cases.
- Magic trailing commas are properly preserved in some (but not all) cases.
- Trivial leading and trailing standalone comments mostly work (although maybe not at the end of a file).
- Inline comments, and comments within expressions, often don’t work -- they work in a few cases, but it’s one-off right now. (We’re probably associating them with the “right” nodes more often than we are actually rendering them in the right place.)
- We don’t properly normalize string quotes. (At present, we just repeat any constants verbatim.)
- We’re mishandling a bunch of wrapping cases (if we treat Black as the reference implementation). Here are a few examples (demonstrating Black's stable behavior):

```py
# In some cases, if the end expression is "self-closing" (functions,
# lists, dictionaries, sets, subscript accesses, and any length-two
# boolean operations that end in these elments), Black
# will wrap like this...
if some_expression and f(
    b,
    c,
    d,
):
    pass

# ...whereas we do this:
if (
    some_expression
    and f(
        b,
        c,
        d,
    )
):
    pass

# If function arguments can fit on a single line, then Black will
# format them like this, rather than exploding them vertically.
if f(
    a, b, c, d, e, f, g, ...
):
    pass
```

- We don’t properly preserve parentheses in all cases. Black preserves parentheses in some but not all cases.
2023-02-15 04:06:35 +00:00
Charlie Marsh
f661c90bd7 Remove dependency on ruff_rowan (#2875)
This PR removes the dependency on `ruff_rowan` (i.e., Rome's fork of rust-analyzer's `rowan`), and in turn, trims out a lot of code in `ruff_formatter` that isn't necessary (or isn't _yet_ necessary) to power the autoformatter.

We may end up pulling some of this back in -- TBD. For example, the autoformatter has its own comment representation right now, but we may eventually want to use the `comments.rs` data structures defined in `rome_formatter`.
2023-02-15 03:54:08 +00:00
Charlie Marsh
5a84df293f Allow printing of consecutive empty lines (#2874) 2023-02-14 22:35:02 -05:00
Charlie Marsh
23d9309111 Remove JetBrains webinar badge (#2910) 2023-02-15 03:28:12 +00:00
Charlie Marsh
98ea94fdb7 Add StaticTextSlice kind to FormatElement enum (#2873)
Given our current parser abstractions, we need the ability to tell `ruff_formatter` to print a pre-defined slice from a fixed string of source code, which we've introduced here as `FormatElement::StaticTextSlice`.
2023-02-14 22:27:52 -05:00
Charlie Marsh
746e1d3436 Add contributors to acknowledgements (#2909) 2023-02-15 03:15:38 +00:00
Charlie Marsh
016ff01a04 Add an FAQ question around Python version support (#2908) 2023-02-15 03:11:44 +00:00
Charlie Marsh
298498e934 Add an Acknowledgements section to the README (#2907) 2023-02-15 00:25:07 +00:00
Charlie Marsh
3ef1c2e303 Add rome_formatter fork as ruff_formatter (#2872)
The Ruff autoformatter is going to be based on an intermediate representation (IR) formatted via [Wadler's algorithm](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf). This is architecturally similar to [Rome](https://github.com/rome/tools), Prettier, [Skip](https://github.com/skiplang/skip/blob/master/src/tools/printer/printer.sk), and others.

This PR adds a fork of the `rome_formatter` crate from [Rome](https://github.com/rome/tools), renamed here to `ruff_formatter`, which provides generic definitions for a formatter IR as well as a generic IR printer. (We've also pulled in `rome_rowan`, `rome_text_size`, and `rome_text_edit`, though some of these will be removed in future PRs.)

Why fork? `rome_formatter` contains code that's specific to Rome's AST representation (e.g., it relies on a fork of rust-analyzer's `rowan`), and we'll likely want to support different abstractions and formatting capabilities (there are already a few changes coming in future PRs). Once we've dropped `ruff_rowan` and trimmed down `ruff_formatter` to the code we currently need, it's also not a huge surface area to maintain and update.
2023-02-14 19:22:55 -05:00
Simon Brugman
ac028cd9f8 [numpy] deprecated type aliases (#2810)
Closes https://github.com/charliermarsh/ruff/issues/2455

Used `NPY` as prefix code as agreed in the issue.
2023-02-14 23:45:12 +00:00
Edgar R. M
c0eb5c28d1 [docs] Add docs for flake8-errmsg (#2888) 2023-02-14 23:21:34 +00:00
Martin Fischer
a77b4566e4 Fix option links in mkdocs rule pages
In 28c9263722 I introduced automatic
linkification of option references in rule documentation,
which automatically converted the following:

    ## Options

    * `namespace-packages`

to:

    ## Options

    * [`namespace-packages`]

    [`namespace-packages`]: ../../settings#namespace-packages

While the above is a correct CommonMark[1] link definition,
what I was missing was that we used mkdocs for our documentation
generation, which as it turns out uses a non-CommonMark-compliant
Markdown parser, namely Python-Markdown, which contrary to CommonMark
doesn't support link definitions containing code tags.

This commit fixes the broken links via a regex hack.

[1]: https://commonmark.org/
2023-02-14 17:56:21 -05:00
Martin Fischer
860993187e Fix link relativization in generate_mkdocs.py 2023-02-14 17:56:21 -05:00
Charlie Marsh
58d4e00604 Add publish = false to unpublished crates (#2905) 2023-02-14 22:41:14 +00:00
Simon Legner
2d95912699 docs: fix ruff generate-shell-completion (#2904) 2023-02-14 22:38:29 +00:00
Simon Brugman
4f927fbacc [flake8-tidy-imports] autofix relative imports (#2891)
Previous fix was bugged. This one is only fixing when the `module_path` is present, making it far more robust.

Closes #2764 and closes #2869
2023-02-14 22:24:59 +00:00
Anders Kaseorg
2e41301520 Switch some quotes to backticks in errors (#2889)
Improves consistency with the style decision in #723, I think.
2023-02-14 22:24:41 +00:00
Martin Fischer
3179fc110d Disable many-to-one mapping for now 2023-02-14 16:16:12 -05:00
Martin Fischer
03ae0118b7 many-to-one 9/9: Update table generation 2023-02-14 16:16:12 -05:00
Martin Fischer
05176890ee many-to-one 8/9: Drop codes from registry
This commit was generated by running:

    fastmod --accept-all '[A-Z]+[0-9]+ => ' '' crates/ruff/src/registry.rs
2023-02-14 16:16:12 -05:00
Martin Fischer
849b947b3e many-to-one 7/9: Update JSON schema 2023-02-14 16:16:12 -05:00
Martin Fischer
c314e10e54 many-to-one 6/9: Implement ruff_macros::map_codes 2023-02-14 16:16:12 -05:00
Martin Fischer
9eda286dcd many-to-one 5/9: Generate codes.rs from registry.rs
# This commit was generated by running the following Python code:
# (followed by `sed -Ei 's/(mod registry;)/\1mod codes;/' crates/ruff/src/lib.rs`
# and `cargo fmt`).

import json
import re
import subprocess

def parse_registry():
    file = open('crates/ruff/src/registry.rs')

    rules = []

    while next(file) != 'ruff_macros::register_rules!(\n':
        continue

    while (line := next(file)) != ');\n':
        line = line.strip().rstrip(',')
        if line.startswith('//') or line.startswith('#['):
            rules.append(line)
            continue
        code, path = line.split(' => ')
        name = path.rsplit('::')[-1]
        rules.append((code, name))

    while (line := next(file)) != 'pub enum Linter {\n':
        continue

    prefixes = []
    prefix2linter = []

    while (line := next(file).strip()) != '}':
        if line.startswith('//'):
            continue
        if line.startswith('#[prefix = '):
            prefixes.append(line.split()[-1].strip('"]'))
        else:
            for prefix in prefixes:
                prefix2linter.append((prefix, line.rstrip(',')))
            prefixes.clear()

    prefix2linter.sort(key = lambda t: len(t[0]), reverse=True)

    return rules, prefix2linter

rules, prefix2linter = parse_registry()

def parse_code(code):
    prefix = re.match('[A-Z]+', code).group()
    if prefix in ('E', 'W'):
        return 'Pycodestyle', code

    for prefix, linter in prefix2linter:
        if code.startswith(prefix):
            return linter, code[len(prefix) :]

    assert False

text = '''
use crate::registry::{Linter, Rule};

pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
    #[allow(clippy::enum_glob_use)]
    use Linter::*;

    Some(match (linter, code) {
'''

for entry in rules:
    if isinstance(entry, str):
        if entry.startswith('//'):
            text += '\n' + entry
        else:
            text += entry
    else:
        namespace, code = parse_code(entry[0])
        text += f'({namespace}, "{code}") => Rule::{entry[1]},'
    text += '\n'

text += '''
       _ => return  None,
    })
}
'''

with open('crates/ruff/src/codes.rs', 'w') as f:
    f.write(text)
2023-02-14 16:16:12 -05:00
Martin Fischer
65a3461519 many-to-one 4/9: Rename define_rule_mapping! to register_rules!
Currently the define_rule_mapping! macro generates both the Rule enum as
well as the RuleCodePrefix enum and the mapping between the two.  After
this commit series the macro will only generate the Rule enum and the
RuleCodePrefix enum and the mapping will be generated by a new map_codes
proc macro, so we rename the macro now to fit its new purpose.
2023-02-14 16:16:12 -05:00
Martin Fischer
1b8d2df3bf many-to-one 3/9: Update RuleSelector::short_code
Same reasoning as for the previous commit ... one &'static str
becomes two &'static str because we split the RuleCodePrefix enum.
Note that the .unwrap() we have to add now, will actually
be removed in the 6th commit.
2023-02-14 16:16:12 -05:00
Martin Fischer
179ead0157 many-to-one 2/9: Newtype Rule::noqa_code return type
Rule::noqa_code previously return a single &'static str,
which was possible because we had one enum listing all
rule code prefixes. This commit series will however split up
the RuleCodePrefix enum into several enums ... so we'll end up
with two &'static str ... this commit wraps the return type
of Rule::noqa_code into a newtype so that we can easily change
it to return two &'static str in the 6th commit of this series.
2023-02-14 16:16:12 -05:00
Martin Fischer
d451c7a506 many-to-one 1/9: Rename Rule::code to Rule::noqa_code
Post this commit series several codes can be mapped to a single rule,
this commit therefore renames Rule::code to Rule::noqa_code,
which is the code that --add-noqa will add to ignore a rule.
2023-02-14 16:16:12 -05:00
Martin Fischer
502ce80c91 many-to-one 0/9: Introduce RuleSelector::Linter variant
We want to remove the variants denoting whole Linters
from the RuleCodePrefix enum, so we have to introduce
a new RuleSelector::Linter variant.
2023-02-14 16:16:12 -05:00
Charlie Marsh
49d22d8fe2 Ignore non-imperative-mood in Google docstring convention (#2900) 2023-02-14 20:42:20 +00:00
Chris May
08e0b76587 Add headers to configutation options (#2896)
This completes the word from the discussion in #2643, adding headers to clarify the `pyproject.toml` and `ruff.toml` sections.
2023-02-14 13:45:59 -05:00
Charlie Marsh
f7515739ac Improve consistency of some rule docs (#2887) 2023-02-14 04:36:37 +00:00
Sawbez
53e810ed3e [docs] Add docs for the entirety of flake8-builtins (#2840) 2023-02-14 04:30:30 +00:00
Charlie Marsh
66a195f805 Extend B904 to else branches (#2886) 2023-02-14 03:58:15 +00:00
Jeremiah England
b8483975a4 docs(SIM114): fix typo in example Python code (#2884) 2023-02-14 03:23:19 +00:00
Charlie Marsh
4dd2032687 Unversion unpublished crates (#2882) 2023-02-14 03:03:49 +00:00
Charlie Marsh
c6c15d5cf9 Avoid unnecessary-else violations in elif branches (#2881)
Long-time source of confusion -- two reports over 1800 issues apart.

Closes #1035.

Closes #2879.
2023-02-14 02:51:12 +00:00
Charlie Marsh
2bf7b35268 Re-enable custom allocators (#2876) 2023-02-14 02:37:22 +00:00
Charlie Marsh
6d1adc85fc Remove autofix for prefer-type-error (#2880) 2023-02-14 02:26:22 +00:00
Martin Fischer
02285c18d1 Remove autogenerated docs/rules/*.md files 2023-02-13 19:34:06 -05:00
Martin Fischer
8120d7c974 Change rule page links in README from GitHub to beta.ruff.rs 2023-02-13 19:34:06 -05:00
Martin Fischer
c858804ed4 refactor: Move docs/ gitignores to docs/.gitignore 2023-02-13 19:34:06 -05:00
Anders Kaseorg
b9d075c252 Alphabetize flake8-raise and flake8-self in documentation (#2871) 2023-02-13 18:03:09 -05:00
Charlie Marsh
7627e840c9 Avoid noqa removal upon unhandled syntax errors (#2864) 2023-02-13 10:37:55 -05:00
Charlie Marsh
3c03e2cb2e Rename flake8-django rules to match convention (#2861) 2023-02-13 15:30:04 +00:00
Charlie Marsh
aeae63b7ea Avoid false-positives for runtime-types in type checking blocks (#2863) 2023-02-13 10:26:34 -05:00
Charlie Marsh
7be17c5f1e Avoid false-positives with multi-byte characters in B005 (#2862) 2023-02-13 15:07:55 +00:00
Charlie Marsh
6128346b08 Re-show --target-version on CLI interface (#2859) 2023-02-13 15:04:11 +00:00
Charlie Marsh
1705574e75 Handle multiple receiver decorators in receiver-decorator (#2858) 2023-02-13 14:57:11 +00:00
Charlie Marsh
15f65fa8d6 Run cargo dev generate-all (#2860) 2023-02-13 14:55:44 +00:00
Ville Skyttä
d1cf0ee52b Remove "blanket" from RUF100 README message (#2844) 2023-02-13 14:43:35 +00:00
Florian Best
32520ff07f ci(gitignore): ignore VIM files (#2856) 2023-02-13 09:29:53 -05:00
Ville Skyttä
3659236580 Remove no longer needed setup.py INP001 ignore (#2846) 2023-02-13 09:00:35 -05:00
Charlie Marsh
dde69d50b5 Move more dependencies into workspace dependencies (#2842) 2023-02-13 04:19:26 +00:00
Charlie Marsh
67198ce7b3 Revert "Run release on tag creation"
This reverts commit c21a5912b9.
2023-02-12 23:11:31 -05:00
409 changed files with 26151 additions and 3450 deletions

View File

@@ -13,6 +13,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt

View File

@@ -27,7 +27,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - x86_64"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist -m ./${{ env.CRATE_NAME }}/Cargo.toml
@@ -50,7 +50,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - universal2"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: "Install built wheel - universal2"
@@ -76,7 +76,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
@@ -102,7 +102,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -128,7 +128,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -166,7 +166,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -201,7 +201,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
@@ -222,40 +222,6 @@ jobs:
name: wheels
path: dist
pypy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
target: [x86_64, aarch64]
python-version:
- "3.7"
- "3.8"
- "3.9"
exclude:
- os: macos-latest
target: aarch64
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: pypy${{ matrix.python-version }}
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: "Install built wheel"
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
@@ -267,7 +233,6 @@ jobs:
- linux-cross
- musllinux
- musllinux-cross
- pypy
steps:
- uses: actions/download-artifact@v3
with:

View File

@@ -2,9 +2,8 @@ name: "[ruff] Release"
on:
workflow_dispatch:
push:
tags:
- '**'
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -30,7 +29,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist
@@ -42,6 +41,19 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-x86_64-apple-darwin.tar.gz
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
macos-universal:
runs-on: macos-latest
steps:
@@ -53,7 +65,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - universal2"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
args: --release --universal2 --out dist
- name: "Install built wheel - universal2"
@@ -64,26 +76,45 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-aarch64-apple-darwin.tar.gz
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
platform:
- target: x86_64-pc-windows-msvc
arch: x64
- target: i686-pc-windows-msvc
arch: x86
- target: aarch64-pc-windows-msvc
arch: x64
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.target }}
architecture: ${{ matrix.platform.arch }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
target: ${{ matrix.platform.target }}
args: --release --out dist
- name: "Install built wheel"
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
shell: bash
run: |
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
@@ -92,12 +123,27 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
shell: bash
run: |
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.zip
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.zip
*.sha256
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, i686]
target:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -107,13 +153,13 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist
- name: "Install built wheel"
if: matrix.target == 'x86_64'
if: ${{ startsWith(matrix.target, 'x86_64') }}
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
@@ -121,12 +167,34 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
linux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
target: [aarch64, armv7, s390x, ppc64le, ppc64]
platform:
- target: aarch64-unknown-linux-gnu
arch: aarch64
- target: armv7-unknown-linux-gnueabihf
arch: armv7
- target: s390x-unknown-linux-gnu
arch: s390x
- target: powerpc64le-unknown-linux-gnu
arch: ppc64le
- target: powerpc64-unknown-linux-gnu
arch: ppc64
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -135,16 +203,16 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
target: ${{ matrix.platform.target }}
manylinux: auto
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2.5.0
if: matrix.target != 'ppc64'
if: matrix.platform.arch != 'ppc64'
name: Install built wheel
with:
arch: ${{ matrix.target }}
arch: ${{ matrix.platform.arch }}
distro: ubuntu20.04
githubToken: ${{ github.token }}
install: |
@@ -158,6 +226,18 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
musllinux:
runs-on: ubuntu-latest
@@ -175,7 +255,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -194,6 +274,18 @@ jobs:
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
musllinux-cross:
runs-on: ubuntu-latest
@@ -212,7 +304,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
@@ -232,42 +324,18 @@ jobs:
with:
name: wheels
path: dist
pypy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
target: [x86_64, aarch64]
python-version:
- "3.7"
- "3.8"
- "3.9"
exclude:
- os: macos-latest
target: aarch64
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: pypy${{ matrix.python-version }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }}
- name: "Install built wheel"
if: matrix.target == 'x86_64'
- name: "Archive binary"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
name: binaries
path: |
*.tar.gz
*.sha256
release:
name: Release
@@ -280,7 +348,6 @@ jobs:
- linux-cross
- musllinux
- musllinux-cross
- pypy
if: "startsWith(github.ref, 'refs/tags/')"
steps:
- uses: actions/download-artifact@v3
@@ -297,3 +364,11 @@ jobs:
- name: "Update pre-commit mirror"
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'
- uses: actions/download-artifact@v3
with:
name: binaries
path: binaries
- name: Release
uses: softprops/action-gh-release@v1
with:
files: binaries/*

8
.gitignore vendored
View File

@@ -1,10 +1,6 @@
# Local cache
.ruff_cache
crates/ruff/resources/test/cpython
docs/*
!docs/rules
!docs/assets
!docs/requirements.txt
mkdocs.yml
.overrides
@@ -191,3 +187,7 @@ cython_debug/
# Visual Studio Code
.vscode/
# VIM
.*.sw?
.sw?

137
Cargo.lock generated
View File

@@ -378,9 +378,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.4"
version = "4.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
dependencies = [
"bitflags",
"clap_derive",
@@ -397,7 +397,7 @@ version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75"
dependencies = [
"clap 4.1.4",
"clap 4.1.6",
]
[[package]]
@@ -406,7 +406,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [
"clap 4.1.4",
"clap 4.1.6",
"clap_complete",
"clap_complete_fig",
]
@@ -417,7 +417,7 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf0c76d8fcf782a1102ccfcd10ca8246e7fdd609c1cd6deddbb96cb638e9bb5c"
dependencies = [
"clap 4.1.4",
"clap 4.1.6",
"clap_complete",
]
@@ -762,6 +762,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "drop_bomb"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
[[package]]
name = "dyn-clone"
version = "1.0.10"
@@ -857,9 +863,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "1.8.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
@@ -893,10 +899,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.246"
version = "0.0.247"
dependencies = [
"anyhow",
"clap 4.1.4",
"clap 4.1.6",
"colored",
"configparser",
"once_cell",
@@ -1539,6 +1545,16 @@ dependencies = [
"syn",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
@@ -1612,7 +1628,7 @@ dependencies = [
"ansi_term",
"anyhow",
"base64 0.20.0",
"clap 4.1.4",
"clap 4.1.6",
"clap_complete",
"env_proxy",
"gethostname",
@@ -1645,6 +1661,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mimalloc"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mime"
version = "0.3.16"
@@ -2421,14 +2446,14 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.246"
version = "0.0.247"
dependencies = [
"anyhow",
"bisection",
"bitflags",
"cfg-if",
"chrono",
"clap 4.1.4",
"clap 4.1.6",
"colored",
"console_error_panic_hook",
"console_log",
@@ -2477,7 +2502,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.246"
version = "0.0.247"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2487,7 +2512,7 @@ dependencies = [
"bitflags",
"cachedir",
"chrono",
"clap 4.1.4",
"clap 4.1.6",
"clap_complete_command",
"clearscreen",
"colored",
@@ -2497,6 +2522,7 @@ dependencies = [
"itertools",
"log",
"mdcat",
"mimalloc",
"notify",
"path-absolutize",
"pulldown-cmark",
@@ -2511,19 +2537,21 @@ dependencies = [
"strum",
"syntect",
"textwrap",
"tikv-jemallocator",
"ureq",
"walkdir",
]
[[package]]
name = "ruff_dev"
version = "0.0.246"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.1.4",
"clap 4.1.6",
"itertools",
"libcst",
"once_cell",
"regex",
"ruff",
"ruff_cli",
"rustpython-common",
@@ -2535,9 +2563,23 @@ dependencies = [
"textwrap",
]
[[package]]
name = "ruff_formatter"
version = "0.0.0"
dependencies = [
"drop_bomb",
"insta",
"ruff_text_size",
"rustc-hash",
"schemars",
"serde",
"tracing",
"unicode-width",
]
[[package]]
name = "ruff_macros"
version = "0.0.246"
version = "0.0.0"
dependencies = [
"itertools",
"proc-macro2",
@@ -2548,13 +2590,39 @@ dependencies = [
[[package]]
name = "ruff_python"
version = "0.0.246"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
[[package]]
name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.1.6",
"insta",
"once_cell",
"ruff_formatter",
"ruff_text_size",
"rustc-hash",
"rustpython-common",
"rustpython-parser",
"test-case",
]
[[package]]
name = "ruff_text_size"
version = "0.0.0"
dependencies = [
"schemars",
"serde",
"serde_test",
"static_assertions",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
@@ -2820,6 +2888,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_test"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3611210d2d67e3513204742004d6ac6f589e521861dabb0f649b070eea8bed9e"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -3156,6 +3233,26 @@ dependencies = [
"weezl",
]
[[package]]
name = "tikv-jemalloc-sys"
version = "0.5.3+5.3.0-patched"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "tikv-jemallocator"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20612db8a13a6c06d57ec83953694185a367e16945f66565e8028d2c0bd76979"
dependencies = [
"libc",
"tikv-jemalloc-sys",
]
[[package]]
name = "time"
version = "0.1.45"
@@ -3892,9 +3989,9 @@ dependencies = [
[[package]]
name = "zune-inflate"
version = "0.2.42"
version = "0.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c473377c11c4a3ac6a2758f944cd336678e9c977aa0abf54f6450cf77e902d6d"
checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40"
dependencies = [
"simd-adler32",
]

View File

@@ -3,9 +3,21 @@ members = ["crates/*"]
default-members = ["crates/ruff", "crates/ruff_cli"]
[workspace.dependencies]
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" }
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 = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
toml = { version = "0.6.0" }
[profile.release]
panic = "abort"

52
LICENSE
View File

@@ -1062,3 +1062,55 @@ are:
"""
- flake8-django, licensed under the GPL license.
- rust-analyzer/text-size, licensed under the MIT license:
"""
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
- rome/tools, licensed under the MIT license:
"""
MIT License
Copyright (c) Rome Tools, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

263
README.md
View File

@@ -7,7 +7,6 @@
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/charliermarsh/ruff/workflows/CI/badge.svg)](https://github.com/charliermarsh/ruff/actions)
[![image](https://img.shields.io/date/1676394000?label=Jetbrains%20Ruff%20Webinar&logo=jetbrains)](https://info.jetbrains.com/PyCharm-Webinar-February14-2023.html)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
@@ -27,7 +26,6 @@ An extremely fast Python linter, written in Rust.
* ⚡️ 10-100x faster than existing linters
* 🐍 Installable via `pip`
* 🤝 Python 3.11 compatibility
* 🛠️ `pyproject.toml` support
* 📦 Built-in caching, to avoid re-analyzing unchanged files
* 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
@@ -49,10 +47,11 @@ 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)
* [Bokeh](https://github.com/bokeh/bokeh)
* [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)
@@ -149,7 +148,9 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
1. [flake8-pyi (PYI)](#flake8-pyi-pyi)
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
1. [flake8-quotes (Q)](#flake8-quotes-q)
1. [flake8-raise (RSE)](#flake8-raise-rse)
1. [flake8-return (RET)](#flake8-return-ret)
1. [flake8-self (SLF)](#flake8-self-slf)
1. [flake8-simplify (SIM)](#flake8-simplify-sim)
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
1. [flake8-type-checking (TCH)](#flake8-type-checking-tch)
@@ -160,14 +161,14 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
1. [Pylint (PL)](#pylint-pl)
1. [tryceratops (TRY)](#tryceratops-try)
1. [flake8-raise (RSE)](#flake8-raise-rse)
1. [flake8-self (SLF)](#flake8-self-slf)
1. [NumPy-specific rules (NPY)](#numpy-specific-rules-npy)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Contributing](#contributing)
1. [Support](#support)
1. [Reference](#reference)
1. [Acknowledgements](#acknowledgements)
1. [License](#license)
## Installation and Usage
@@ -232,7 +233,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.246'
rev: 'v0.0.247'
hooks:
- id: ruff
```
@@ -242,7 +243,7 @@ Or, to enable autofix:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.246'
rev: 'v0.0.247'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
@@ -254,9 +255,11 @@ Or, to enable autofix:
<!-- Begin section: Configuration -->
Ruff is configurable both via `pyproject.toml` and the command line. For a full list of configurable
Ruff is configurable both via `pyproject.toml`, `ruff.toml`, and the command line. For a full list of configurable
options, see the [API reference](#reference).
### Configure via `pyproject.toml`
If left unspecified, the default configuration is equivalent to:
```toml
@@ -362,6 +365,8 @@ If you're wondering how to configure Ruff, here are some **recommended guideline
* 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](#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`:
@@ -437,19 +442,34 @@ 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]
--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
--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>
@@ -656,7 +676,7 @@ Ruff supports two command-line flags that alter its exit code behavior:
### Autocompletion
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
by `ruff completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,
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,
@@ -710,13 +730,13 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
| F506 | percent-format-mixed-positional-and-named | `%`-format string has mixed positional and named placeholders | |
| F507 | percent-format-positional-count-mismatch | `%`-format string has {wanted} placeholder(s) but {got} substitution(s) | |
| F508 | percent-format-star-requires-sequence | `%`-format string `*` specifier requires sequence | |
| F509 | percent-format-unsupported-format-character | `%`-format string has unsupported format character '{char}' | |
| F509 | percent-format-unsupported-format-character | `%`-format string has unsupported format character `{char}` | |
| F521 | string-dot-format-invalid-format | `.format` call has invalid format string: {message} | |
| F522 | string-dot-format-extra-named-arguments | `.format` call has unused named argument(s): {message} | 🛠 |
| F523 | string-dot-format-extra-positional-arguments | `.format` call has unused arguments at position(s): {message} | |
| F524 | string-dot-format-missing-arguments | `.format` call is missing argument(s) for placeholder(s): {message} | |
| F525 | string-dot-format-mixing-automatic | `.format` string mixes automatic and manual numbering | |
| F541 | [f-string-missing-placeholders](https://github.com/charliermarsh/ruff/blob/main/docs/rules/f-string-missing-placeholders.md) | f-string without any placeholders | 🛠 |
| F541 | [f-string-missing-placeholders](https://beta.ruff.rs/docs/rules/f-string-missing-placeholders/) | f-string without any placeholders | 🛠 |
| F601 | multi-value-repeated-key-literal | Dictionary key literal `{name}` repeated | 🛠 |
| F602 | multi-value-repeated-key-variable | Dictionary key `{name}` repeated | 🛠 |
| F621 | expressions-in-star-assignment | Too many expressions in star-unpacking assignment | |
@@ -735,7 +755,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
| F821 | undefined-name | Undefined name `{name}` | |
| F822 | undefined-export | Undefined name `{name}` in `__all__` | |
| F823 | undefined-local | Local variable `{name}` referenced before assignment | |
| F841 | [unused-variable](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unused-variable.md) | Local variable `{name}` is assigned to but never used | 🛠 |
| F841 | [unused-variable](https://beta.ruff.rs/docs/rules/unused-variable/) | Local variable `{name}` is assigned to but never used | 🛠 |
| F842 | unused-annotation | Local variable `{name}` is annotated but never used | |
| F901 | raise-not-implemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
@@ -759,7 +779,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/) on PyPI.
| E713 | not-in-test | Test for membership should be `not in` | 🛠 |
| E714 | not-is-test | Test for object identity should be `is not` | 🛠 |
| E721 | type-comparison | Do not compare types, use `isinstance()` | |
| E722 | [bare-except](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bare-except.md) | Do not use bare `except` | |
| E722 | [bare-except](https://beta.ruff.rs/docs/rules/bare-except/) | Do not use bare `except` | |
| E731 | lambda-assignment | Do not assign a `lambda` expression, use a `def` | 🛠 |
| E741 | ambiguous-variable-name | Ambiguous variable name: `{name}` | |
| E742 | ambiguous-class-name | Ambiguous class name: `{name}` | |
@@ -773,7 +793,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/) on PyPI.
| ---- | ---- | ------- | --- |
| W292 | no-new-line-at-end-of-file | No newline at end of file | 🛠 |
| W505 | doc-line-too-long | Doc line too long ({length} > {limit} characters) | |
| W605 | invalid-escape-sequence | Invalid escape sequence: '\{char}' | 🛠 |
| W605 | invalid-escape-sequence | Invalid escape sequence: `\{char}` | 🛠 |
### mccabe (C90)
@@ -781,7 +801,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C901 | [complex-structure](https://github.com/charliermarsh/ruff/blob/main/docs/rules/complex-structure.md) | `{name}` is too complex ({complexity}) | |
| C901 | [complex-structure](https://beta.ruff.rs/docs/rules/complex-structure/) | `{name}` is too complex ({complexity}) | |
### isort (I)
@@ -789,8 +809,8 @@ For more, see [isort](https://pypi.org/project/isort/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I001 | [unsorted-imports](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unsorted-imports.md) | Import block is un-sorted or un-formatted | 🛠 |
| I002 | [missing-required-import](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-required-import.md) | Missing required import: `{name}` | 🛠 |
| I001 | [unsorted-imports](https://beta.ruff.rs/docs/rules/unsorted-imports/) | Import block is un-sorted or un-formatted | 🛠 |
| I002 | [missing-required-import](https://beta.ruff.rs/docs/rules/missing-required-import/) | Missing required import: `{name}` | 🛠 |
### pep8-naming (N)
@@ -844,8 +864,8 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
| D213 | multi-line-summary-second-line | Multi-line docstring summary should start at the second line | 🛠 |
| D214 | section-not-over-indented | Section is over-indented ("{name}") | 🛠 |
| D215 | section-underline-not-over-indented | Section underline is over-indented ("{name}") | 🛠 |
| D300 | triple-single-quotes | Use """triple double quotes""" | |
| D301 | escape-sequence-in-docstring | Use r""" if any backslashes in a docstring | |
| D300 | triple-single-quotes | Use triple double quotes `"""` | |
| D301 | escape-sequence-in-docstring | Use `r"""` if any backslashes in a docstring | |
| D400 | ends-in-period | First line should end with a period | 🛠 |
| D401 | non-imperative-mood | First line of docstring should be in imperative mood: "{first_line}" | |
| D402 | no-signature | First line should not be the function's signature | |
@@ -932,17 +952,17 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | [missing-type-function-argument](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-function-argument.md) | Missing type annotation for function argument `{name}` | |
| ANN002 | [missing-type-args](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-args.md) | Missing type annotation for `*{name}` | |
| ANN003 | [missing-type-kwargs](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-kwargs.md) | Missing type annotation for `**{name}` | |
| ANN101 | [missing-type-self](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-self.md) | Missing type annotation for `{name}` in method | |
| ANN102 | [missing-type-cls](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-type-cls.md) | Missing type annotation for `{name}` in classmethod | |
| ANN201 | [missing-return-type-public-function](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-public-function.md) | Missing return type annotation for public function `{name}` | |
| ANN202 | [missing-return-type-private-function](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-private-function.md) | Missing return type annotation for private function `{name}` | |
| ANN204 | [missing-return-type-special-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-special-method.md) | Missing return type annotation for special method `{name}` | 🛠 |
| ANN205 | [missing-return-type-static-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-static-method.md) | Missing return type annotation for staticmethod `{name}` | |
| ANN206 | [missing-return-type-class-method](https://github.com/charliermarsh/ruff/blob/main/docs/rules/missing-return-type-class-method.md) | Missing return type annotation for classmethod `{name}` | |
| ANN401 | [any-type](https://github.com/charliermarsh/ruff/blob/main/docs/rules/any-type.md) | Dynamically typed expressions (typing.Any) are disallowed in `{name}` | |
| ANN001 | [missing-type-function-argument](https://beta.ruff.rs/docs/rules/missing-type-function-argument/) | Missing type annotation for function argument `{name}` | |
| ANN002 | [missing-type-args](https://beta.ruff.rs/docs/rules/missing-type-args/) | Missing type annotation for `*{name}` | |
| ANN003 | [missing-type-kwargs](https://beta.ruff.rs/docs/rules/missing-type-kwargs/) | Missing type annotation for `**{name}` | |
| ANN101 | [missing-type-self](https://beta.ruff.rs/docs/rules/missing-type-self/) | Missing type annotation for `{name}` in method | |
| ANN102 | [missing-type-cls](https://beta.ruff.rs/docs/rules/missing-type-cls/) | Missing type annotation for `{name}` in classmethod | |
| ANN201 | [missing-return-type-public-function](https://beta.ruff.rs/docs/rules/missing-return-type-public-function/) | Missing return type annotation for public function `{name}` | |
| ANN202 | [missing-return-type-private-function](https://beta.ruff.rs/docs/rules/missing-return-type-private-function/) | Missing return type annotation for private function `{name}` | |
| ANN204 | [missing-return-type-special-method](https://beta.ruff.rs/docs/rules/missing-return-type-special-method/) | Missing return type annotation for special method `{name}` | 🛠 |
| ANN205 | [missing-return-type-static-method](https://beta.ruff.rs/docs/rules/missing-return-type-static-method/) | Missing return type annotation for staticmethod `{name}` | |
| ANN206 | [missing-return-type-class-method](https://beta.ruff.rs/docs/rules/missing-return-type-class-method/) | Missing return type annotation for classmethod `{name}` | |
| ANN401 | [any-type](https://beta.ruff.rs/docs/rules/any-type/) | Dynamically typed expressions (typing.Any) are disallowed in `{name}` | |
### flake8-bandit (S)
@@ -961,12 +981,12 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
| S110 | try-except-pass | `try`-`except`-`pass` detected, consider logging the exception | |
| S112 | try-except-continue | `try`-`except`-`continue` detected, consider logging the exception | |
| S113 | request-without-timeout | Probable use of requests call with timeout set to `{value}` | |
| S324 | hashlib-insecure-hash-function | Probable use of insecure hash functions in `hashlib`: "{}" | |
| S324 | hashlib-insecure-hash-function | Probable use of insecure hash functions in `hashlib`: `{string}` | |
| S501 | request-with-no-cert-validation | Probable use of `{string}` call with `verify=False` disabling SSL certificate checks | |
| S506 | unsafe-yaml-load | Probable use of unsafe loader `{name}` with `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`. | |
| S508 | snmp-insecure-version | The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. | |
| S509 | snmp-weak-cryptography | You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. | |
| S608 | [hardcoded-sql-expression](https://github.com/charliermarsh/ruff/blob/main/docs/rules/hardcoded-sql-expression.md) | Possible SQL injection vector through string-based query construction | |
| S608 | [hardcoded-sql-expression](https://beta.ruff.rs/docs/rules/hardcoded-sql-expression/) | Possible SQL injection vector through string-based query construction | |
| S612 | logging-config-insecure-listen | Use of insecure `logging.config.listen` detected | |
| S701 | jinja2-autoescape-false | Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function. | |
@@ -1009,7 +1029,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) on PyPI
| B014 | duplicate-handler-exception | Exception handler with duplicate exception: `{name}` | 🛠 |
| B015 | useless-comparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
| B016 | cannot-raise-literal | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
| B017 | [assert-raises-exception](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | `assertRaises(Exception)` should be considered evil | |
| B017 | [assert-raises-exception](https://beta.ruff.rs/docs/rules/assert-raises-exception/) | `assertRaises(Exception)` should be considered evil | |
| B018 | useless-expression | Found useless expression. Either assign it to a variable or remove it. | |
| B019 | cached-instance-method | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks | |
| B020 | loop-variable-overrides-iterator | Loop control variable `{name}` overrides iterable it iterates | |
@@ -1029,9 +1049,9 @@ For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/) on Py
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| A001 | builtin-variable-shadowing | Variable `{name}` is shadowing a python builtin | |
| A002 | builtin-argument-shadowing | Argument `{name}` is shadowing a python builtin | |
| A003 | builtin-attribute-shadowing | Class attribute `{name}` is shadowing a python builtin | |
| A001 | [builtin-variable-shadowing](https://beta.ruff.rs/docs/rules/builtin-variable-shadowing/) | Variable `{name}` is shadowing a python builtin | |
| A002 | [builtin-argument-shadowing](https://beta.ruff.rs/docs/rules/builtin-argument-shadowing/) | Argument `{name}` is shadowing a python builtin | |
| A003 | [builtin-attribute-shadowing](https://beta.ruff.rs/docs/rules/builtin-attribute-shadowing/) | Class attribute `{name}` is shadowing a python builtin | |
### flake8-commas (COM)
@@ -1049,9 +1069,9 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | [unnecessary-generator-list](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-list.md) | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | [unnecessary-generator-set](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-set.md) | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | [unnecessary-generator-dict](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-dict.md) | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C400 | [unnecessary-generator-list](https://beta.ruff.rs/docs/rules/unnecessary-generator-list/) | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | [unnecessary-generator-set](https://beta.ruff.rs/docs/rules/unnecessary-generator-set/) | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | [unnecessary-generator-dict](https://beta.ruff.rs/docs/rules/unnecessary-generator-dict/) | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | unnecessary-list-comprehension-set | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | unnecessary-list-comprehension-dict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | unnecessary-literal-set | Unnecessary `{obj_type}` literal (rewrite as a `set` literal) | 🛠 |
@@ -1060,11 +1080,11 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
| C409 | unnecessary-literal-within-tuple-call | Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a `tuple` literal) | 🛠 |
| C410 | unnecessary-literal-within-list-call | Unnecessary `{literal}` literal passed to `list()` (remove the outer call to `list()`) | 🛠 |
| C411 | unnecessary-list-call | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | [unnecessary-call-around-sorted](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-call-around-sorted.md) | Unnecessary `{func}` call around `sorted()` | 🛠 |
| C414 | [unnecessary-double-cast-or-process](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-double-cast-or-process.md) | Unnecessary `{inner}` call within `{outer}()` | 🛠 |
| C413 | [unnecessary-call-around-sorted](https://beta.ruff.rs/docs/rules/unnecessary-call-around-sorted/) | Unnecessary `{func}` call around `sorted()` | 🛠 |
| C414 | [unnecessary-double-cast-or-process](https://beta.ruff.rs/docs/rules/unnecessary-double-cast-or-process/) | Unnecessary `{inner}` call within `{outer}()` | 🛠 |
| C415 | unnecessary-subscript-reversal | Unnecessary subscript reversal of iterable within `{func}()` | |
| C416 | unnecessary-comprehension | Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`) | 🛠 |
| C417 | [unnecessary-map](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-map.md) | Unnecessary `map` usage (rewrite using a generator expression) | 🛠 |
| C417 | [unnecessary-map](https://beta.ruff.rs/docs/rules/unnecessary-map/) | Unnecessary `map` usage (rewrite using a generator expression) | 🛠 |
### flake8-datetimez (DTZ)
@@ -1096,9 +1116,9 @@ For more, see [flake8-django](https://pypi.org/project/flake8-django/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| DJ001 | [model-string-field-nullable](https://github.com/charliermarsh/ruff/blob/main/docs/rules/model-string-field-nullable.md) | Avoid using `null=True` on string-based fields such as {field_name} | |
| DJ008 | [model-dunder-str](https://github.com/charliermarsh/ruff/blob/main/docs/rules/model-dunder-str.md) | Model does not define `__str__` method | |
| DJ013 | [receiver-decorator-checker](https://github.com/charliermarsh/ruff/blob/main/docs/rules/receiver-decorator-checker.md) | `@receiver` decorator must be on top of all the other decorators | |
| DJ001 | [nullable-model-string-field](https://beta.ruff.rs/docs/rules/nullable-model-string-field/) | Avoid using `null=True` on string-based fields such as {field_name} | |
| DJ008 | [model-without-dunder-str](https://beta.ruff.rs/docs/rules/model-without-dunder-str/) | Model does not define `__str__` method | |
| DJ013 | [non-leading-receiver-decorator](https://beta.ruff.rs/docs/rules/non-leading-receiver-decorator/) | `@receiver` decorator must be on top of all the other decorators | |
### flake8-errmsg (EM)
@@ -1106,9 +1126,9 @@ For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| EM101 | raw-string-in-exception | Exception must not use a string literal, assign to variable first | |
| EM102 | f-string-in-exception | Exception must not use an f-string literal, assign to variable first | |
| EM103 | dot-format-in-exception | Exception must not use a `.format()` string directly, assign to variable first | |
| EM101 | [raw-string-in-exception](https://beta.ruff.rs/docs/rules/raw-string-in-exception/) | Exception must not use a string literal, assign to variable first | |
| EM102 | [f-string-in-exception](https://beta.ruff.rs/docs/rules/f-string-in-exception/) | Exception must not use an f-string literal, assign to variable first | |
| EM103 | [dot-format-in-exception](https://beta.ruff.rs/docs/rules/dot-format-in-exception/) | Exception must not use a `.format()` string directly, assign to variable first | |
### flake8-executable (EXE)
@@ -1118,7 +1138,7 @@ For more, see [flake8-executable](https://pypi.org/project/flake8-executable/) o
| ---- | ---- | ------- | --- |
| EXE001 | shebang-not-executable | Shebang is present but file is not executable | |
| EXE002 | shebang-missing-executable-file | The file is executable but no shebang is present | |
| EXE003 | shebang-python | Shebang should contain "python" | |
| EXE003 | shebang-python | Shebang should contain `python` | |
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
@@ -1138,7 +1158,7 @@ For more, see [flake8-import-conventions](https://github.com/joaopalmeiro/flake8
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ICN001 | [unconventional-import-alias](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | `{name}` should be imported as `{asname}` | |
| ICN001 | [unconventional-import-alias](https://beta.ruff.rs/docs/rules/unconventional-import-alias/) | `{name}` should be imported as `{asname}` | |
### flake8-logging-format (G)
@@ -1161,7 +1181,7 @@ For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/) on
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| INP001 | [implicit-namespace-package](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
| INP001 | [implicit-namespace-package](https://beta.ruff.rs/docs/rules/implicit-namespace-package/) | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
### flake8-pie (PIE)
@@ -1192,9 +1212,9 @@ For more, see [flake8-pyi](https://pypi.org/project/flake8-pyi/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PYI001 | [prefix-type-params](https://github.com/charliermarsh/ruff/blob/main/docs/rules/prefix-type-params.md) | Name of private `{kind}` must start with _ | |
| PYI007 | [unrecognized-platform-check](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unrecognized-platform-check.md) | Unrecognized sys.platform check | |
| PYI008 | [unrecognized-platform-name](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unrecognized-platform-name.md) | Unrecognized platform `{platform}` | |
| PYI001 | [prefix-type-params](https://beta.ruff.rs/docs/rules/prefix-type-params/) | Name of private `{kind}` must start with `_` | |
| PYI007 | [unrecognized-platform-check](https://beta.ruff.rs/docs/rules/unrecognized-platform-check/) | Unrecognized `sys.platform` check | |
| PYI008 | [unrecognized-platform-name](https://beta.ruff.rs/docs/rules/unrecognized-platform-name/) | Unrecognized platform `{platform}` | |
### flake8-pytest-style (PT)
@@ -1234,10 +1254,18 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| Q000 | [bad-quotes-inline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | Double quotes found but single quotes preferred | 🛠 |
| Q001 | [bad-quotes-multiline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | Double quote multiline found but single quotes preferred | 🛠 |
| Q002 | [bad-quotes-docstring](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | Double quote docstring found but single quotes preferred | 🛠 |
| Q003 | [avoidable-escaped-quote](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoidable-escaped-quote.md) | Change outer quotes to avoid escaping inner quotes | 🛠 |
| Q000 | [bad-quotes-inline-string](https://beta.ruff.rs/docs/rules/bad-quotes-inline-string/) | Double quotes found but single quotes preferred | 🛠 |
| Q001 | [bad-quotes-multiline-string](https://beta.ruff.rs/docs/rules/bad-quotes-multiline-string/) | Double quote multiline found but single quotes preferred | 🛠 |
| Q002 | [bad-quotes-docstring](https://beta.ruff.rs/docs/rules/bad-quotes-docstring/) | Double quote docstring found but single quotes preferred | 🛠 |
| Q003 | [avoidable-escaped-quote](https://beta.ruff.rs/docs/rules/avoidable-escaped-quote/) | Change outer quotes to avoid escaping inner quotes | 🛠 |
### flake8-raise (RSE)
For more, see [flake8-raise](https://pypi.org/project/flake8-raise/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| RSE102 | unnecessary-paren-on-raise-exception | Unnecessary parentheses on raised exception | 🛠 |
### flake8-return (RET)
@@ -1254,6 +1282,14 @@ For more, see [flake8-return](https://pypi.org/project/flake8-return/) on PyPI.
| RET507 | superfluous-else-continue | Unnecessary `{branch}` after `continue` statement | |
| RET508 | superfluous-else-break | Unnecessary `{branch}` after `break` statement | |
### flake8-self (SLF)
For more, see [flake8-self](https://pypi.org/project/flake8-self/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| SLF001 | [private-member-access](https://beta.ruff.rs/docs/rules/private-member-access/) | Private member accessed: `{access}` | |
### flake8-simplify (SIM)
For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on PyPI.
@@ -1270,7 +1306,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on Py
| SIM110 | convert-loop-to-any | Use `{any}` instead of `for` loop | 🛠 |
| SIM111 | convert-loop-to-all | Use `{all}` instead of `for` loop | 🛠 |
| SIM112 | use-capital-environment-variables | Use capitalized environment variable `{expected}` instead of `{original}` | 🛠 |
| SIM114 | [if-with-same-arms](https://github.com/charliermarsh/ruff/blob/main/docs/rules/if-with-same-arms.md) | Combine `if` branches using logical `or` operator | |
| SIM114 | [if-with-same-arms](https://beta.ruff.rs/docs/rules/if-with-same-arms/) | Combine `if` branches using logical `or` operator | |
| SIM115 | open-file-with-context-handler | Use context handler for opening files | |
| SIM117 | multiple-with-statements | Use a single `with` statement with multiple contexts instead of nested `with` statements | 🛠 |
| SIM118 | key-in-dict | Use `{key} in {dict}` instead of `{key} in {dict}.keys()` | 🛠 |
@@ -1293,8 +1329,8 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TID251 | [banned-api](https://github.com/charliermarsh/ruff/blob/main/docs/rules/banned-api.md) | `{name}` is banned: {message} | |
| TID252 | [relative-imports](https://github.com/charliermarsh/ruff/blob/main/docs/rules/relative-imports.md) | Relative imports from parent modules are banned | 🛠 |
| TID251 | [banned-api](https://beta.ruff.rs/docs/rules/banned-api/) | `{name}` is banned: {message} | |
| TID252 | [relative-imports](https://beta.ruff.rs/docs/rules/relative-imports/) | Relative imports from parent modules are banned | 🛠 |
### flake8-type-checking (TCH)
@@ -1358,7 +1394,7 @@ For more, see [eradicate](https://pypi.org/project/eradicate/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ERA001 | [commented-out-code](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | Found commented-out code | 🛠 |
| ERA001 | [commented-out-code](https://beta.ruff.rs/docs/rules/commented-out-code/) | Found commented-out code | 🛠 |
### pandas-vet (PD)
@@ -1366,7 +1402,7 @@ For more, see [pandas-vet](https://pypi.org/project/pandas-vet/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PD002 | [use-of-inplace-argument](https://github.com/charliermarsh/ruff/blob/main/docs/rules/use-of-inplace-argument.md) | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
| PD002 | [use-of-inplace-argument](https://beta.ruff.rs/docs/rules/use-of-inplace-argument/) | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | |
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | |
| PD007 | use-of-dot-ix | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
@@ -1405,13 +1441,13 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PLE0100 | [yield-in-init](https://github.com/charliermarsh/ruff/blob/main/docs/rules/yield-in-init.md) | `__init__` method is a generator | |
| PLE0100 | [yield-in-init](https://beta.ruff.rs/docs/rules/yield-in-init/) | `__init__` method is a generator | |
| PLE0117 | nonlocal-without-binding | Nonlocal name `{name}` found without binding | |
| PLE0118 | used-prior-global-declaration | Name `{name}` is used prior to global declaration on line {line} | |
| PLE0604 | invalid-all-object | Invalid object in `__all__`, must contain only strings | |
| PLE0605 | invalid-all-format | Invalid format for `__all__`, must be `tuple` or `list` | |
| PLE1142 | await-outside-async | `await` should be used within an async function | |
| PLE1307 | [bad-string-format-type](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-string-format-type.md) | Format type does not match argument type | |
| PLE1307 | [bad-string-format-type](https://beta.ruff.rs/docs/rules/bad-string-format-type/) | Format type does not match argument type | |
| PLE1310 | bad-str-strip-call | String `{strip}` call contains duplicate characters (did you mean `{removal}`?) | |
| PLE2502 | bidirectional-unicode | Contains control characters that can permit obfuscated code | |
@@ -1434,7 +1470,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PLW0120 | useless-else-on-loop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
| PLW0120 | useless-else-on-loop | `else` clause on loop without a `break` statement; remove the `else` and de-indent all the code inside it | |
| PLW0602 | global-variable-not-assigned | Using global for `{name}` but no assignment is done | |
### tryceratops (TRY)
@@ -1443,41 +1479,32 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TRY002 | [raise-vanilla-class](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | Create your own exception | |
| TRY002 | [raise-vanilla-class](https://beta.ruff.rs/docs/rules/raise-vanilla-class/) | Create your own exception | |
| TRY003 | raise-vanilla-args | Avoid specifying long messages outside the exception class | |
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 |
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | |
| TRY200 | reraise-no-cause | Use `raise from` to specify exception cause | |
| TRY201 | verbose-raise | Use `raise` without specifying exception name | |
| TRY300 | try-consider-else | Consider moving this statement to an `else` block | |
| TRY301 | raise-within-try | Abstract `raise` to an inner function | |
| TRY400 | error-instead-of-exception | Use `logging.exception` instead of `logging.error` | |
### flake8-raise (RSE)
For more, see [flake8-raise](https://pypi.org/project/flake8-raise/) on PyPI.
### NumPy-specific rules (NPY)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| RSE102 | unnecessary-paren-on-raise-exception | Unnecessary parentheses on raised exception | 🛠 |
### flake8-self (SLF)
For more, see [flake8-self](https://pypi.org/project/flake8-self/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| SLF001 | private-member-access | Private member accessed: `{access}` | |
| NPY001 | [numpy-deprecated-type-alias](https://beta.ruff.rs/docs/rules/numpy-deprecated-type-alias/) | Type alias `np.{type_name}` is deprecated, replace with builtin type | 🛠 |
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| RUF001 | ambiguous-unicode-character-string | String contains ambiguous unicode character '{confusable}' (did you mean '{representant}'?) | 🛠 |
| RUF002 | ambiguous-unicode-character-docstring | Docstring contains ambiguous unicode character '{confusable}' (did you mean '{representant}'?) | 🛠 |
| RUF003 | ambiguous-unicode-character-comment | Comment contains ambiguous unicode character '{confusable}' (did you mean '{representant}'?) | 🛠 |
| RUF001 | ambiguous-unicode-character-string | String contains ambiguous unicode character `{confusable}` (did you mean `{representant}`?) | 🛠 |
| RUF002 | ambiguous-unicode-character-docstring | Docstring contains ambiguous unicode character `{confusable}` (did you mean `{representant}`?) | 🛠 |
| RUF003 | ambiguous-unicode-character-comment | Comment contains ambiguous unicode character `{confusable}` (did you mean `{representant}`?) | 🛠 |
| RUF004 | keyword-argument-before-star-argument | Keyword argument `{name}` must come after starred arguments | |
| RUF005 | unpack-instead-of-concatenating-to-collection-literal | Consider `{expr}` instead of concatenation | 🛠 |
| RUF100 | unused-noqa | Unused blanket `noqa` directive | 🛠 |
| RUF006 | [asyncio-dangling-task](https://beta.ruff.rs/docs/rules/asyncio-dangling-task/) | Store a reference to the return value of `asyncio.create_task` | |
| RUF100 | unused-noqa | Unused `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->
@@ -1880,6 +1907,22 @@ implemented in [pyupgrade](https://pypi.org/project/pyupgrade/) ([#827](https://
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, feel free to file an Issue.
### What versions of Python does Ruff support?
Ruff can lint code for any Python version from 3.7 onwards. However, Ruff lacks support for a few
language features that were introduced in Python 3.10 and later. Specifically, Ruff does not
support:
- "Structural Pattern Matching" ([PEP 622](https://peps.python.org/pep-0622/)), introduced in Python 3.10.
- "Exception Groups and except* ([PEP 654](https://www.python.org/dev/peps/pep-0654/)), introduced in Python 3.11.
Support for these features is planned.
Ruff does not support Python 2. Ruff _may_ run on pre-Python 3.7 code, although such versions
are not officially supported (e.g., Ruff does _not_ respect type comments).
Ruff is installable under any Python version from 3.7 onwards.
### Do I need to install Rust to use Ruff?
Nope! Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
@@ -4010,6 +4053,32 @@ keep-runtime-typing = true
<!-- End section: Settings -->
<!-- Begin section: Acknowledgements -->
## Acknowledgements
Ruff's linter draws on both the APIs and implementation details of many other
tools in the Python ecosystem, especially [Flake8](https://github.com/PyCQA/flake8), [Pyflakes](https://github.com/PyCQA/pyflakes),
[pycodestyle](https://github.com/PyCQA/pycodestyle), [pydocstyle](https://github.com/PyCQA/pydocstyle),
[pyupgrade](https://github.com/asottile/pyupgrade), and [isort](https://github.com/PyCQA/isort).
In some cases, Ruff includes a "direct" Rust port of the corresponding tool.
We're grateful to the maintainers of these tools for their work, and for all
the value they've provided to the Python community.
Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
and again draws on both the APIs and implementation details of [Rome](https://github.com/rome/tools),
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).
Ruff is also influenced by a number of tools outside the Python ecosystem, like
[Clippy](https://github.com/rust-lang/rust-clippy) and [ESLint](https://github.com/eslint/eslint).
Ruff is the beneficiary of a large number of [contributors](https://github.com/charliermarsh/ruff/graphs/contributors).
Ruff is released under the MIT license.
<!-- End section: Acknowledgements -->
## License
MIT

View File

@@ -1,2 +1,6 @@
[files]
extend-exclude = ["snapshots"]
extend-exclude = ["snapshots", "black"]
[default.extend-words]
trivias = "trivias"
hel = "hel"

7
clippy.toml Normal file
View File

@@ -0,0 +1,7 @@
doc-valid-idents = [
"StackOverflow",
"CodeQL",
"IPython",
"NumPy",
"..",
]

View File

@@ -1,19 +1,19 @@
[package]
name = "flake8-to-ruff"
version = "0.0.246"
version = "0.0.247"
edition = "2021"
[dependencies]
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
anyhow = { workspace = true }
clap = { workspace = true }
colored = { version = "2.0.0" }
configparser = { version = "3.0.2" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
once_cell = { workspace = true }
regex = { workspace = true }
ruff = { path = "../ruff", default-features = false }
rustc-hash = { version = "1.1.0" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
toml = { version = "0.6.0" }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
toml = { workspace = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.246"
version = "0.0.247"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -16,12 +16,12 @@ crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
anyhow = { version = "1.0.66" }
anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" }
cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive", "env"] }
clap = { workspace = true, features = ["derive", "env", "string"] }
colored = { version = "2.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
@@ -29,32 +29,32 @@ glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
itertools = { version = "0.10.5" }
itertools = { workspace = true }
libcst = { workspace = true }
log = { version = "0.4.17" }
natord = { version = "1.0.9" }
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
once_cell = { workspace = true }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.246", path = "../ruff_macros" }
ruff_python = { version = "0.0.246", path = "../ruff_python" }
rustc-hash = { version = "1.1.0" }
regex = { workspace = true }
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
rustc-hash = { workspace = true }
rustpython-common = { workspace = true }
rustpython-parser = { workspace = true }
schemars = { version = "0.8.11" }
schemars = { workspace = true }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
serde = { workspace = true }
shellexpand = { version = "3.0.0" }
smallvec = { version = "1.10.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml = { version = "0.6.0" }
toml = { workspace = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support

View File

@@ -103,3 +103,7 @@ class Foo:
@classmethod
def foo(cls, a: int, b: int) -> int:
pass
# ANN101
def foo(self, /, a: int, b: int) -> int:
pass

View File

@@ -17,6 +17,9 @@ s.rstrip(".facebook.com") # warning
s.rstrip("e") # no warning
s.rstrip("\n\t ") # no warning
s.rstrip(r"\n\t ") # warning
s.strip("a") # no warning
s.strip("") # no warning
s.strip("ああ") # warning
from somewhere import other_type, strip

View File

@@ -1,6 +1,6 @@
"""
Should emit:
B904 - on lines 10, 11 and 16
B904 - on lines 10, 11, 16, 62, and 64
"""
try:
@@ -53,3 +53,12 @@ except ImportError:
raise ValueError
except ValueError:
raise
try:
...
except Exception as e:
if ...:
raise RuntimeError("boom!")
else:
raise RuntimeError("bang!")

View File

@@ -12,7 +12,16 @@ class IncorrectModel(models.Model):
urlfield = models.URLField(max_length=255, null=True)
class IncorrectModelWithAliasedBase(DjangoModel):
class IncorrectModelWithAlias(DjangoModel):
charfield = DjangoModel.CharField(max_length=255, null=True)
textfield = SmthCharField(max_length=255, null=True)
slugfield = models.SlugField(max_length=255, null=True)
emailfield = models.EmailField(max_length=255, null=True)
filepathfield = models.FilePathField(max_length=255, null=True)
urlfield = models.URLField(max_length=255, null=True)
class IncorrectModelWithoutSuperclass:
charfield = DjangoModel.CharField(max_length=255, null=True)
textfield = SmthCharField(max_length=255, null=True)
slugfield = models.SlugField(max_length=255, null=True)

View File

@@ -15,3 +15,23 @@ def correct_pre_save_handler():
@receiver(pre_save, sender=MyModel)
def incorrect_pre_save_handler():
pass
@receiver(pre_save, sender=MyModel)
@receiver(pre_save, sender=MyModel)
@test_decorator
def correct_multiple():
pass
@receiver(pre_save, sender=MyModel)
@receiver(pre_save, sender=MyModel)
def correct_multiple():
pass
@receiver(pre_save, sender=MyModel)
@test_decorator
@receiver(pre_save, sender=MyModel)
def incorrect_multiple():
pass

View File

@@ -136,3 +136,24 @@ def nested2(x, y, z):
break
else:
a = z
def elif1(x, y, w, z):
for i in x:
if i > y:
a = z
elif i > w:
break
else:
a = z
def elif2(x, y, w, z):
for i in x:
if i > y:
a = z
else:
if i > w:
break
else:
a = z

View File

@@ -35,6 +35,13 @@ class Foo(metaclass=BazMeta):
return None
if self.bar()._private: # SLF001
return None
if Bar._private_thing: # SLF001
return None
if Foo._private_thing:
return None
Foo = Bar()
if Foo._private_thing: # SLF001
return None
return self.bar
def public_func(self):
@@ -61,3 +68,4 @@ print(foo._private_func()) # SLF001
print(foo.__really_private_func(1)) # SLF001
print(foo.bar._private) # SLF001
print(foo()._private_thing) # SLF001
print(foo()._private_thing__) # SLF001

View File

@@ -18,8 +18,6 @@ from \
..parent\
import \
world_hello
# TID252 (without autofix; too many levels up)
from ..... import ultragrantparent
from ...... import ultragrantparent
from ....... import ultragrantparent

View File

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

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Final, Literal, TypeAlias
RatingKey: TypeAlias = Literal["good", "fair", "poor"]
RATING_KEYS: Final[tuple[RatingKey, ...]] = ("good", "fair", "poor")

View File

@@ -0,0 +1,20 @@
import numpy as npy
import numpy as np
import numpy
# Error
npy.bool
npy.int
if dtype == np.object:
...
result = result.select_dtypes([np.byte, np.ubyte, np.short, np.ushort, np.int, np.long])
pdf = pd.DataFrame(
data=[[1, 2, 3]],
columns=["a", "b", "c"],
dtype=numpy.object,
)
_ = arr.astype(np.int)

View File

@@ -0,0 +1,52 @@
import asyncio
# Error
def f():
asyncio.create_task(coordinator.ws_connect()) # Error
# OK
def f():
background_tasks = set()
for i in range(10):
task = asyncio.create_task(some_coro(param=i))
# Add task to the set. This creates a strong reference.
background_tasks.add(task)
# To prevent keeping references to finished tasks forever,
# make each task remove its own reference from the set after
# completion:
task.add_done_callback(background_tasks.discard)
# OK
def f():
ctx.task = asyncio.create_task(make_request())
# OK
def f():
tasks.append(asyncio.create_task(self._populate_collection(coll, coll_info)))
# OK
def f():
asyncio.wait([asyncio.create_task(client.close()) for client in clients.values()])
# OK
def f():
tasks = [asyncio.create_task(task) for task in tasks]
# Ok (false negative)
def f():
task = asyncio.create_task(coordinator.ws_connect())
# Ok (potential false negative)
def f():
do_nothing_with_the_task(asyncio.create_task(coordinator.ws_connect()))

View File

@@ -596,7 +596,7 @@ pub fn has_comments<T>(located: &Located<T>, locator: &Locator) -> bool {
/// Returns `true` if a [`Range`] includes at least one comment.
pub fn has_comments_in(range: Range, locator: &Locator) -> bool {
for tok in lexer::make_tokenizer(locator.slice_source_code_range(&range)) {
for tok in lexer::make_tokenizer(locator.slice(&range)) {
match tok {
Ok((_, tok, _)) => {
if matches!(tok, Tok::Comment(..)) {
@@ -723,7 +723,7 @@ where
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// Don't recurse.
}
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
StmtKind::Return { value } => self.returns.push(value.as_deref()),
_ => visitor::walk_stmt(self, stmt),
}
}
@@ -756,7 +756,7 @@ pub fn to_relative(absolute: Location, base: Location) -> Location {
/// Return `true` if a [`Located`] has leading content.
pub fn match_leading_content<T>(located: &Located<T>, locator: &Locator) -> bool {
let range = Range::new(Location::new(located.location.row(), 0), located.location);
let prefix = locator.slice_source_code_range(&range);
let prefix = locator.slice(&range);
prefix.chars().any(|char| !char.is_whitespace())
}
@@ -766,7 +766,7 @@ pub fn match_trailing_content<T>(located: &Located<T>, locator: &Locator) -> boo
located.end_location.unwrap(),
Location::new(located.end_location.unwrap().row() + 1, 0),
);
let suffix = locator.slice_source_code_range(&range);
let suffix = locator.slice(&range);
for char in suffix.chars() {
if char == '#' {
return false;
@@ -784,7 +784,7 @@ pub fn match_trailing_comment<T>(located: &Located<T>, locator: &Locator) -> Opt
located.end_location.unwrap(),
Location::new(located.end_location.unwrap().row() + 1, 0),
);
let suffix = locator.slice_source_code_range(&range);
let suffix = locator.slice(&range);
for (i, char) in suffix.chars().enumerate() {
if char == '#' {
return Some(i);
@@ -798,8 +798,7 @@ pub fn match_trailing_comment<T>(located: &Located<T>, locator: &Locator) -> Opt
/// Return the number of trailing empty lines following a statement.
pub fn count_trailing_lines(stmt: &Stmt, locator: &Locator) -> usize {
let suffix =
locator.slice_source_code_at(Location::new(stmt.end_location.unwrap().row() + 1, 0));
let suffix = locator.skip(Location::new(stmt.end_location.unwrap().row() + 1, 0));
suffix
.lines()
.take_while(|line| line.trim().is_empty())
@@ -808,7 +807,7 @@ pub fn count_trailing_lines(stmt: &Stmt, locator: &Locator) -> usize {
/// Return the range of the first parenthesis pair after a given [`Location`].
pub fn match_parens(start: Location, locator: &Locator) -> Option<Range> {
let contents = locator.slice_source_code_at(start);
let contents = locator.skip(start);
let mut fix_start = None;
let mut fix_end = None;
let mut count: usize = 0;
@@ -843,7 +842,7 @@ pub fn identifier_range(stmt: &Stmt, locator: &Locator) -> Range {
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
) {
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
let contents = locator.slice(&Range::from_located(stmt));
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt.location).flatten() {
if matches!(tok, Tok::Name { .. }) {
return Range::new(start, end);
@@ -871,7 +870,7 @@ pub fn binding_range(binding: &Binding, locator: &Locator) -> Range {
// Return the ranges of `Name` tokens within a specified node.
pub fn find_names<T>(located: &Located<T>, locator: &Locator) -> Vec<Range> {
let contents = locator.slice_source_code_range(&Range::from_located(located));
let contents = locator.slice(&Range::from_located(located));
lexer::make_tokenizer_located(contents, located.location)
.flatten()
.filter(|(_, tok, _)| matches!(tok, Tok::Name { .. }))
@@ -890,8 +889,7 @@ pub fn excepthandler_name_range(handler: &Excepthandler, locator: &Locator) -> O
match (name, type_) {
(Some(_), Some(type_)) => {
let type_end_location = type_.end_location.unwrap();
let contents =
locator.slice_source_code_range(&Range::new(type_end_location, body[0].location));
let contents = locator.slice(&Range::new(type_end_location, body[0].location));
let range = lexer::make_tokenizer_located(contents, type_end_location)
.flatten()
.tuple_windows()
@@ -915,7 +913,7 @@ pub fn except_range(handler: &Excepthandler, locator: &Locator) -> Range {
.expect("Expected body to be non-empty")
.location
};
let contents = locator.slice_source_code_range(&Range {
let contents = locator.slice(&Range {
location: handler.location,
end_location: end,
});
@@ -932,7 +930,7 @@ pub fn except_range(handler: &Excepthandler, locator: &Locator) -> Range {
/// Find f-strings that don't contain any formatted values in a `JoinedStr`.
pub fn find_useless_f_strings(expr: &Expr, locator: &Locator) -> Vec<(Range, Range)> {
let contents = locator.slice_source_code_range(&Range::from_located(expr));
let contents = locator.slice(&Range::from_located(expr));
lexer::make_tokenizer_located(contents, expr.location)
.flatten()
.filter_map(|(location, tok, end_location)| match tok {
@@ -940,7 +938,7 @@ pub fn find_useless_f_strings(expr: &Expr, locator: &Locator) -> Vec<(Range, Ran
kind: StringKind::FString | StringKind::RawFString,
..
} => {
let first_char = locator.slice_source_code_range(&Range {
let first_char = locator.slice(&Range {
location,
end_location: Location::new(location.row(), location.column() + 1),
});
@@ -980,7 +978,7 @@ pub fn else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> {
.expect("Expected body to be non-empty")
.end_location
.unwrap();
let contents = locator.slice_source_code_range(&Range {
let contents = locator.slice(&Range {
location: body_end,
end_location: orelse
.first()
@@ -1002,7 +1000,7 @@ pub fn else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> {
/// Return the `Range` of the first `Tok::Colon` token in a `Range`.
pub fn first_colon_range(range: Range, locator: &Locator) -> Option<Range> {
let contents = locator.slice_source_code_range(&range);
let contents = locator.slice(&range);
let range = lexer::make_tokenizer_located(contents, range.location)
.flatten()
.find(|(_, kind, _)| matches!(kind, Tok::Colon))
@@ -1032,7 +1030,7 @@ pub fn elif_else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> {
[stmt, ..] => stmt.location,
_ => return None,
};
let contents = locator.slice_source_code_range(&Range::new(start, end));
let contents = locator.slice(&Range::new(start, end));
let range = lexer::make_tokenizer_located(contents, start)
.flatten()
.find(|(_, kind, _)| matches!(kind, Tok::Elif | Tok::Else))

View File

@@ -8,7 +8,7 @@ use crate::source_code::Locator;
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(locator: &'a Locator, located: &'a Located<T>) -> Option<&'a str> {
let range = Range::from_located(located);
let indentation = locator.slice_source_code_range(&Range::new(
let indentation = locator.slice(&Range::new(
Location::new(range.location.row(), 0),
Location::new(range.location.row(), range.location.column()),
));

View File

@@ -81,7 +81,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
/// of a multi-statement line.
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
let contents = locator.slice_source_code_at(stmt.end_location.unwrap());
let contents = locator.skip(stmt.end_location.unwrap());
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
if trimmed.starts_with(';') {
@@ -104,7 +104,7 @@ fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
/// Find the next valid break for a `Stmt` after a semicolon.
fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
let contents = locator.slice_source_code_at(start_location);
let contents = locator.skip(start_location);
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
// Skip past any continuations.
@@ -136,7 +136,7 @@ fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
/// Return `true` if a `Stmt` occurs at the end of a file.
fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
let contents = locator.slice_source_code_at(stmt.end_location.unwrap());
let contents = locator.skip(stmt.end_location.unwrap());
contents.is_empty()
}
@@ -209,7 +209,7 @@ pub fn remove_unused_imports<'a>(
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let module_text = locator.slice(&Range::from_located(stmt));
let mut tree = match_module(module_text)?;
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
@@ -339,7 +339,7 @@ pub fn remove_argument(
remove_parentheses: bool,
) -> Result<Fix> {
// TODO(sbrugman): Preserve trailing comments.
let contents = locator.slice_source_code_at(stmt_at);
let contents = locator.skip(stmt_at);
let mut fix_start = None;
let mut fix_end = None;

View File

@@ -54,7 +54,7 @@ fn apply_fixes<'a>(
}
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice_source_code_range(&Range::new(last_pos, fix.location));
let slice = locator.slice(&Range::new(last_pos, fix.location));
output.push_str(slice);
// Add the patch itself.
@@ -67,7 +67,7 @@ fn apply_fixes<'a>(
}
// Add the remaining content.
let slice = locator.slice_source_code_at(last_pos);
let slice = locator.skip(last_pos);
output.push_str(slice);
(output, fixed)
@@ -78,14 +78,14 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String {
let mut output = String::with_capacity(locator.len());
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice_source_code_range(&Range::new(Location::new(1, 0), fix.location));
let slice = locator.slice(&Range::new(Location::new(1, 0), fix.location));
output.push_str(slice);
// Add the patch itself.
output.push_str(&fix.content);
// Add the remaining content.
let slice = locator.slice_source_code_at(fix.end_location);
let slice = locator.skip(fix.end_location);
output.push_str(slice);
output

View File

@@ -38,8 +38,8 @@ use crate::rules::{
flake8_django, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise,
flake8_return, flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
flake8_unused_arguments, flake8_use_pathlib, mccabe, numpy, pandas_vet, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
use crate::settings::{flags, Settings};
@@ -50,6 +50,7 @@ use crate::{autofix, docstrings, noqa, visibility};
const GLOBAL_SCOPE_INDEX: usize = 0;
type DeferralContext<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
type AnnotationContext = (bool, bool);
#[allow(clippy::struct_excessive_bools)]
pub struct Checker<'a> {
@@ -84,8 +85,8 @@ pub struct Checker<'a> {
pub(crate) scopes: Vec<Scope<'a>>,
pub(crate) scope_stack: Vec<usize>,
pub(crate) dead_scopes: Vec<(usize, Vec<usize>)>,
deferred_string_type_definitions: Vec<(Range, &'a str, bool, DeferralContext<'a>)>,
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>,
deferred_string_type_definitions: Vec<(Range, &'a str, AnnotationContext, DeferralContext<'a>)>,
deferred_type_definitions: Vec<(&'a Expr, AnnotationContext, DeferralContext<'a>)>,
deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>,
deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>,
deferred_for_loops: Vec<(&'a Stmt, DeferralContext<'a>)>,
@@ -464,14 +465,16 @@ where
body,
..
} => {
if self.settings.rules.enabled(&Rule::ReceiverDecoratorChecker) {
if let Some(diagnostic) =
flake8_django::rules::receiver_decorator_checker(decorator_list, |expr| {
self.resolve_call_path(expr)
})
{
self.diagnostics.push(diagnostic);
}
if self
.settings
.rules
.enabled(&Rule::NonLeadingReceiverDecorator)
{
self.diagnostics
.extend(flake8_django::rules::non_leading_receiver_decorator(
decorator_list,
|expr| self.resolve_call_path(expr),
));
}
if self.settings.rules.enabled(&Rule::AmbiguousFunctionName) {
if let Some(diagnostic) =
@@ -780,15 +783,15 @@ where
decorator_list,
body,
} => {
if self.settings.rules.enabled(&Rule::ModelStringFieldNullable) {
if self.settings.rules.enabled(&Rule::NullableModelStringField) {
self.diagnostics
.extend(flake8_django::rules::model_string_field_nullable(
self, bases, body,
.extend(flake8_django::rules::nullable_model_string_field(
self, body,
));
}
if self.settings.rules.enabled(&Rule::ModelDunderStr) {
if self.settings.rules.enabled(&Rule::ModelWithoutDunderStr) {
if let Some(diagnostic) =
flake8_django::rules::model_dunder_str(self, bases, body, stmt)
flake8_django::rules::model_without_dunder_str(self, bases, body, stmt)
{
self.diagnostics.push(diagnostic);
}
@@ -1317,8 +1320,8 @@ where
stmt,
level.as_ref(),
module.as_deref(),
self.module_path.as_ref(),
&self.settings.flake8_tidy_imports.ban_relative_imports,
self.path,
)
{
self.diagnostics.push(diagnostic);
@@ -1557,12 +1560,7 @@ where
pyflakes::rules::assert_tuple(self, stmt, test);
}
if self.settings.rules.enabled(&Rule::AssertFalse) {
flake8_bugbear::rules::assert_false(
self,
stmt,
test,
msg.as_ref().map(|expr| &**expr),
);
flake8_bugbear::rules::assert_false(self, stmt, test, msg.as_deref());
}
if self.settings.rules.enabled(&Rule::Assert) {
self.diagnostics
@@ -1797,6 +1795,13 @@ where
{
flake8_simplify::rules::use_capital_environment_variables(self, value);
}
if self.settings.rules.enabled(&Rule::AsyncioDanglingTask) {
if let Some(diagnostic) = ruff::rules::asyncio_dangling_task(value, |expr| {
self.resolve_call_path(expr)
}) {
self.diagnostics.push(diagnostic);
}
}
}
_ => {}
}
@@ -2067,13 +2072,13 @@ where
self.deferred_string_type_definitions.push((
Range::from_located(expr),
value,
self.in_annotation,
(self.in_annotation, self.in_type_checking_block),
(self.scope_stack.clone(), self.parents.clone()),
));
} else {
self.deferred_type_definitions.push((
expr,
self.in_annotation,
(self.in_annotation, self.in_type_checking_block),
(self.scope_stack.clone(), self.parents.clone()),
));
}
@@ -2142,6 +2147,9 @@ where
if self.settings.rules.enabled(&Rule::TypingTextStrAlias) {
pyupgrade::rules::typing_text_str_alias(self, expr);
}
if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) {
numpy::rules::deprecated_type_alias(self, expr);
}
// Ex) List[...]
if !self.in_deferred_string_type_definition
@@ -2208,6 +2216,9 @@ where
if self.settings.rules.enabled(&Rule::TypingTextStrAlias) {
pyupgrade::rules::typing_text_str_alias(self, expr);
}
if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) {
numpy::rules::deprecated_type_alias(self, expr);
}
if self.settings.rules.enabled(&Rule::RewriteMockImport) {
pyupgrade::rules::rewrite_mock_attribute(self, expr);
}
@@ -3188,7 +3199,7 @@ where
self.deferred_string_type_definitions.push((
Range::from_located(expr),
value,
self.in_annotation,
(self.in_annotation, self.in_type_checking_block),
(self.scope_stack.clone(), self.parents.clone()),
));
}
@@ -4493,12 +4504,13 @@ impl<'a> Checker<'a> {
fn check_deferred_type_definitions(&mut self) {
self.deferred_type_definitions.reverse();
while let Some((expr, in_annotation, (scopes, parents))) =
while let Some((expr, (in_annotation, in_type_checking_block), (scopes, parents))) =
self.deferred_type_definitions.pop()
{
self.scope_stack = scopes;
self.parents = parents;
self.in_annotation = in_annotation;
self.in_type_checking_block = in_type_checking_block;
self.in_type_definition = true;
self.in_deferred_type_definition = true;
self.visit_expr(expr);
@@ -4513,7 +4525,7 @@ impl<'a> Checker<'a> {
{
let mut stacks = vec![];
self.deferred_string_type_definitions.reverse();
while let Some((range, expression, in_annotation, context)) =
while let Some((range, expression, (in_annotation, in_type_checking_block), deferral)) =
self.deferred_string_type_definitions.pop()
{
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
@@ -4524,7 +4536,7 @@ impl<'a> Checker<'a> {
}
relocate_expr(&mut expr, range);
allocator.push(expr);
stacks.push((in_annotation, context));
stacks.push(((in_annotation, in_type_checking_block), deferral));
} else {
if self
.settings
@@ -4540,10 +4552,13 @@ impl<'a> Checker<'a> {
}
}
}
for (expr, (in_annotation, (scopes, parents))) in allocator.iter().zip(stacks) {
for (expr, ((in_annotation, in_type_checking_block), (scopes, parents))) in
allocator.iter().zip(stacks)
{
self.scope_stack = scopes;
self.parents = parents;
self.in_annotation = in_annotation;
self.in_type_checking_block = in_type_checking_block;
self.in_type_definition = true;
self.in_deferred_string_type_definition = true;
self.visit_expr(expr);
@@ -5213,10 +5228,8 @@ impl<'a> Checker<'a> {
// Extract a `Docstring` from a `Definition`.
let expr = definition.docstring.unwrap();
let contents = self
.locator
.slice_source_code_range(&Range::from_located(expr));
let indentation = self.locator.slice_source_code_range(&Range::new(
let contents = self.locator.slice(&Range::from_located(expr));
let indentation = self.locator.slice(&Range::new(
Location::new(expr.location.row(), 0),
Location::new(expr.location.row(), expr.location.column()),
));

View File

@@ -52,8 +52,7 @@ pub fn check_logical_lines(
// Extract the indentation level.
let start_loc = line.mapping[0].1;
let start_line = locator
.slice_source_code_range(&Range::new(Location::new(start_loc.row(), 0), start_loc));
let start_line = locator.slice(&Range::new(Location::new(start_loc.row(), 0), start_loc));
let indent_level = expand_indent(start_line);
let indent_size = 4;

View File

@@ -4,6 +4,7 @@ use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::codes::NoqaCode;
use crate::fix::Fix;
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
@@ -20,7 +21,7 @@ pub fn check_noqa(
settings: &Settings,
autofix: flags::Autofix,
) {
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut noqa_directives: IntMap<usize, (Directive, Vec<NoqaCode>)> = IntMap::default();
let mut ignored = vec![];
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
@@ -55,13 +56,13 @@ pub fn check_noqa(
});
match noqa {
(Directive::All(..), matches) => {
matches.push(diagnostic.kind.rule().code());
matches.push(diagnostic.kind.rule().noqa_code());
ignored.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().code());
matches.push(diagnostic.kind.rule().noqa_code());
ignored.push(index);
continue;
}
@@ -82,12 +83,12 @@ pub fn check_noqa(
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![]));
match noqa {
(Directive::All(..), matches) => {
matches.push(diagnostic.kind.rule().code());
matches.push(diagnostic.kind.rule().noqa_code());
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().code());
matches.push(diagnostic.kind.rule().noqa_code());
ignored.push(index);
}
}
@@ -128,12 +129,12 @@ pub fn check_noqa(
let mut self_ignore = false;
for code in codes {
let code = get_redirect_target(code).unwrap_or(code);
if code == Rule::UnusedNOQA.code() {
if Rule::UnusedNOQA.noqa_code() == code {
self_ignore = true;
break;
}
if matches.contains(&code) || settings.external.contains(code) {
if matches.iter().any(|m| *m == code) || settings.external.contains(code) {
valid_codes.push(code);
} else {
if let Ok(rule) = Rule::from_code(code) {

609
crates/ruff/src/codes.rs Normal file
View File

@@ -0,0 +1,609 @@
use crate::registry::{Linter, Rule};
#[ruff_macros::map_codes]
pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[allow(clippy::enum_glob_use)]
use Linter::*;
Some(match (linter, code) {
// pycodestyle errors
(Pycodestyle, "E101") => Rule::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E111") => Rule::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E112") => Rule::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E113") => Rule::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E114") => Rule::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E115") => Rule::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E116") => Rule::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E117") => Rule::OverIndented,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E201") => Rule::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E202") => Rule::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E223") => Rule::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E265") => Rule::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E266") => Rule::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E271") => Rule::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E272") => Rule::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E273") => Rule::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E274") => Rule::TabBeforeKeyword,
(Pycodestyle, "E401") => Rule::MultipleImportsOnOneLine,
(Pycodestyle, "E402") => Rule::ModuleImportNotAtTopOfFile,
(Pycodestyle, "E501") => Rule::LineTooLong,
(Pycodestyle, "E701") => Rule::MultipleStatementsOnOneLineColon,
(Pycodestyle, "E702") => Rule::MultipleStatementsOnOneLineSemicolon,
(Pycodestyle, "E703") => Rule::UselessSemicolon,
(Pycodestyle, "E711") => Rule::NoneComparison,
(Pycodestyle, "E712") => Rule::TrueFalseComparison,
(Pycodestyle, "E713") => Rule::NotInTest,
(Pycodestyle, "E714") => Rule::NotIsTest,
(Pycodestyle, "E721") => Rule::TypeComparison,
(Pycodestyle, "E722") => Rule::BareExcept,
(Pycodestyle, "E731") => Rule::LambdaAssignment,
(Pycodestyle, "E741") => Rule::AmbiguousVariableName,
(Pycodestyle, "E742") => Rule::AmbiguousClassName,
(Pycodestyle, "E743") => Rule::AmbiguousFunctionName,
(Pycodestyle, "E902") => Rule::IOError,
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,
// pyflakes
(Pyflakes, "401") => Rule::UnusedImport,
(Pyflakes, "402") => Rule::ImportShadowedByLoopVar,
(Pyflakes, "403") => Rule::ImportStar,
(Pyflakes, "404") => Rule::LateFutureImport,
(Pyflakes, "405") => Rule::ImportStarUsage,
(Pyflakes, "406") => Rule::ImportStarNotPermitted,
(Pyflakes, "407") => Rule::FutureFeatureNotDefined,
(Pyflakes, "501") => Rule::PercentFormatInvalidFormat,
(Pyflakes, "502") => Rule::PercentFormatExpectedMapping,
(Pyflakes, "503") => Rule::PercentFormatExpectedSequence,
(Pyflakes, "504") => Rule::PercentFormatExtraNamedArguments,
(Pyflakes, "505") => Rule::PercentFormatMissingArgument,
(Pyflakes, "506") => Rule::PercentFormatMixedPositionalAndNamed,
(Pyflakes, "507") => Rule::PercentFormatPositionalCountMismatch,
(Pyflakes, "508") => Rule::PercentFormatStarRequiresSequence,
(Pyflakes, "509") => Rule::PercentFormatUnsupportedFormatCharacter,
(Pyflakes, "521") => Rule::StringDotFormatInvalidFormat,
(Pyflakes, "522") => Rule::StringDotFormatExtraNamedArguments,
(Pyflakes, "523") => Rule::StringDotFormatExtraPositionalArguments,
(Pyflakes, "524") => Rule::StringDotFormatMissingArguments,
(Pyflakes, "525") => Rule::StringDotFormatMixingAutomatic,
(Pyflakes, "541") => Rule::FStringMissingPlaceholders,
(Pyflakes, "601") => Rule::MultiValueRepeatedKeyLiteral,
(Pyflakes, "602") => Rule::MultiValueRepeatedKeyVariable,
(Pyflakes, "621") => Rule::ExpressionsInStarAssignment,
(Pyflakes, "622") => Rule::TwoStarredExpressions,
(Pyflakes, "631") => Rule::AssertTuple,
(Pyflakes, "632") => Rule::IsLiteral,
(Pyflakes, "633") => Rule::InvalidPrintSyntax,
(Pyflakes, "634") => Rule::IfTuple,
(Pyflakes, "701") => Rule::BreakOutsideLoop,
(Pyflakes, "702") => Rule::ContinueOutsideLoop,
(Pyflakes, "704") => Rule::YieldOutsideFunction,
(Pyflakes, "706") => Rule::ReturnOutsideFunction,
(Pyflakes, "707") => Rule::DefaultExceptNotLast,
(Pyflakes, "722") => Rule::ForwardAnnotationSyntaxError,
(Pyflakes, "811") => Rule::RedefinedWhileUnused,
(Pyflakes, "821") => Rule::UndefinedName,
(Pyflakes, "822") => Rule::UndefinedExport,
(Pyflakes, "823") => Rule::UndefinedLocal,
(Pyflakes, "841") => Rule::UnusedVariable,
(Pyflakes, "842") => Rule::UnusedAnnotation,
(Pyflakes, "901") => Rule::RaiseNotImplemented,
// pylint
(Pylint, "E0100") => Rule::YieldInInit,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "E1307") => Rule::BadStringFormatType,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0915") => Rule::TooManyStatements,
// flake8-builtins
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
(Flake8Builtins, "002") => Rule::BuiltinArgumentShadowing,
(Flake8Builtins, "003") => Rule::BuiltinAttributeShadowing,
// flake8-bugbear
(Flake8Bugbear, "002") => Rule::UnaryPrefixIncrement,
(Flake8Bugbear, "003") => Rule::AssignmentToOsEnviron,
(Flake8Bugbear, "004") => Rule::UnreliableCallableCheck,
(Flake8Bugbear, "005") => Rule::StripWithMultiCharacters,
(Flake8Bugbear, "006") => Rule::MutableArgumentDefault,
(Flake8Bugbear, "007") => Rule::UnusedLoopControlVariable,
(Flake8Bugbear, "008") => Rule::FunctionCallArgumentDefault,
(Flake8Bugbear, "009") => Rule::GetAttrWithConstant,
(Flake8Bugbear, "010") => Rule::SetAttrWithConstant,
(Flake8Bugbear, "011") => Rule::AssertFalse,
(Flake8Bugbear, "012") => Rule::JumpStatementInFinally,
(Flake8Bugbear, "013") => Rule::RedundantTupleInExceptionHandler,
(Flake8Bugbear, "014") => Rule::DuplicateHandlerException,
(Flake8Bugbear, "015") => Rule::UselessComparison,
(Flake8Bugbear, "016") => Rule::CannotRaiseLiteral,
(Flake8Bugbear, "017") => Rule::AssertRaisesException,
(Flake8Bugbear, "018") => Rule::UselessExpression,
(Flake8Bugbear, "019") => Rule::CachedInstanceMethod,
(Flake8Bugbear, "020") => Rule::LoopVariableOverridesIterator,
(Flake8Bugbear, "021") => Rule::FStringDocstring,
(Flake8Bugbear, "022") => Rule::UselessContextlibSuppress,
(Flake8Bugbear, "023") => Rule::FunctionUsesLoopVariable,
(Flake8Bugbear, "024") => Rule::AbstractBaseClassWithoutAbstractMethod,
(Flake8Bugbear, "025") => Rule::DuplicateTryBlockException,
(Flake8Bugbear, "026") => Rule::StarArgUnpackingAfterKeywordArg,
(Flake8Bugbear, "027") => Rule::EmptyMethodWithoutAbstractDecorator,
(Flake8Bugbear, "904") => Rule::RaiseWithoutFromInsideExcept,
(Flake8Bugbear, "905") => Rule::ZipWithoutExplicitStrict,
// flake8-blind-except
(Flake8BlindExcept, "001") => Rule::BlindExcept,
// flake8-comprehensions
(Flake8Comprehensions, "00") => Rule::UnnecessaryGeneratorList,
(Flake8Comprehensions, "01") => Rule::UnnecessaryGeneratorSet,
(Flake8Comprehensions, "02") => Rule::UnnecessaryGeneratorDict,
(Flake8Comprehensions, "03") => Rule::UnnecessaryListComprehensionSet,
(Flake8Comprehensions, "04") => Rule::UnnecessaryListComprehensionDict,
(Flake8Comprehensions, "05") => Rule::UnnecessaryLiteralSet,
(Flake8Comprehensions, "06") => Rule::UnnecessaryLiteralDict,
(Flake8Comprehensions, "08") => Rule::UnnecessaryCollectionCall,
(Flake8Comprehensions, "09") => Rule::UnnecessaryLiteralWithinTupleCall,
(Flake8Comprehensions, "10") => Rule::UnnecessaryLiteralWithinListCall,
(Flake8Comprehensions, "11") => Rule::UnnecessaryListCall,
(Flake8Comprehensions, "13") => Rule::UnnecessaryCallAroundSorted,
(Flake8Comprehensions, "14") => Rule::UnnecessaryDoubleCastOrProcess,
(Flake8Comprehensions, "15") => Rule::UnnecessarySubscriptReversal,
(Flake8Comprehensions, "16") => Rule::UnnecessaryComprehension,
(Flake8Comprehensions, "17") => Rule::UnnecessaryMap,
// flake8-debugger
(Flake8Debugger, "0") => Rule::Debugger,
// mccabe
(McCabe, "1") => Rule::ComplexStructure,
// flake8-tidy-imports
(Flake8TidyImports, "251") => Rule::BannedApi,
(Flake8TidyImports, "252") => Rule::RelativeImports,
// flake8-return
(Flake8Return, "501") => Rule::UnnecessaryReturnNone,
(Flake8Return, "502") => Rule::ImplicitReturnValue,
(Flake8Return, "503") => Rule::ImplicitReturn,
(Flake8Return, "504") => Rule::UnnecessaryAssign,
(Flake8Return, "505") => Rule::SuperfluousElseReturn,
(Flake8Return, "506") => Rule::SuperfluousElseRaise,
(Flake8Return, "507") => Rule::SuperfluousElseContinue,
(Flake8Return, "508") => Rule::SuperfluousElseBreak,
// flake8-implicit-str-concat
(Flake8ImplicitStrConcat, "001") => Rule::SingleLineImplicitStringConcatenation,
(Flake8ImplicitStrConcat, "002") => Rule::MultiLineImplicitStringConcatenation,
(Flake8ImplicitStrConcat, "003") => Rule::ExplicitStringConcatenation,
// flake8-print
(Flake8Print, "1") => Rule::PrintFound,
(Flake8Print, "3") => Rule::PPrintFound,
// flake8-quotes
(Flake8Quotes, "000") => Rule::BadQuotesInlineString,
(Flake8Quotes, "001") => Rule::BadQuotesMultilineString,
(Flake8Quotes, "002") => Rule::BadQuotesDocstring,
(Flake8Quotes, "003") => Rule::AvoidableEscapedQuote,
// flake8-annotations
(Flake8Annotations, "001") => Rule::MissingTypeFunctionArgument,
(Flake8Annotations, "002") => Rule::MissingTypeArgs,
(Flake8Annotations, "003") => Rule::MissingTypeKwargs,
(Flake8Annotations, "101") => Rule::MissingTypeSelf,
(Flake8Annotations, "102") => Rule::MissingTypeCls,
(Flake8Annotations, "201") => Rule::MissingReturnTypePublicFunction,
(Flake8Annotations, "202") => Rule::MissingReturnTypePrivateFunction,
(Flake8Annotations, "204") => Rule::MissingReturnTypeSpecialMethod,
(Flake8Annotations, "205") => Rule::MissingReturnTypeStaticMethod,
(Flake8Annotations, "206") => Rule::MissingReturnTypeClassMethod,
(Flake8Annotations, "401") => Rule::AnyType,
// flake8-2020
(Flake82020, "101") => Rule::SysVersionSlice3Referenced,
(Flake82020, "102") => Rule::SysVersion2Referenced,
(Flake82020, "103") => Rule::SysVersionCmpStr3,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3Referenced,
(Flake82020, "202") => Rule::SixPY3Referenced,
(Flake82020, "203") => Rule::SysVersionInfo1CmpInt,
(Flake82020, "204") => Rule::SysVersionInfoMinorCmpInt,
(Flake82020, "301") => Rule::SysVersion0Referenced,
(Flake82020, "302") => Rule::SysVersionCmpStr10,
(Flake82020, "303") => Rule::SysVersionSlice1Referenced,
// flake8-simplify
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
(Flake8Simplify, "102") => Rule::CollapsibleIf,
(Flake8Simplify, "103") => Rule::NeedlessBool,
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
(Flake8Simplify, "108") => Rule::UseTernaryOperator,
(Flake8Simplify, "109") => Rule::CompareWithTuple,
(Flake8Simplify, "110") => Rule::ConvertLoopToAny,
(Flake8Simplify, "111") => Rule::ConvertLoopToAll,
(Flake8Simplify, "112") => Rule::UseCapitalEnvironmentVariables,
(Flake8Simplify, "114") => Rule::IfWithSameArms,
(Flake8Simplify, "115") => Rule::OpenFileWithContextHandler,
(Flake8Simplify, "117") => Rule::MultipleWithStatements,
(Flake8Simplify, "118") => Rule::KeyInDict,
(Flake8Simplify, "201") => Rule::NegateEqualOp,
(Flake8Simplify, "202") => Rule::NegateNotEqualOp,
(Flake8Simplify, "208") => Rule::DoubleNegation,
(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, "300") => Rule::YodaConditions,
(Flake8Simplify, "401") => Rule::DictGetWithDefault,
// pyupgrade
(Pyupgrade, "001") => Rule::UselessMetaclassType,
(Pyupgrade, "003") => Rule::TypeOfPrimitive,
(Pyupgrade, "004") => Rule::UselessObjectInheritance,
(Pyupgrade, "005") => Rule::DeprecatedUnittestAlias,
(Pyupgrade, "006") => Rule::DeprecatedCollectionType,
(Pyupgrade, "007") => Rule::TypingUnion,
(Pyupgrade, "008") => Rule::SuperCallWithParameters,
(Pyupgrade, "009") => Rule::UTF8EncodingDeclaration,
(Pyupgrade, "010") => Rule::UnnecessaryFutureImport,
(Pyupgrade, "011") => Rule::LRUCacheWithoutParameters,
(Pyupgrade, "012") => Rule::UnnecessaryEncodeUTF8,
(Pyupgrade, "013") => Rule::ConvertTypedDictFunctionalToClass,
(Pyupgrade, "014") => Rule::ConvertNamedTupleFunctionalToClass,
(Pyupgrade, "015") => Rule::RedundantOpenModes,
(Pyupgrade, "017") => Rule::DatetimeTimezoneUTC,
(Pyupgrade, "018") => Rule::NativeLiterals,
(Pyupgrade, "019") => Rule::TypingTextStrAlias,
(Pyupgrade, "020") => Rule::OpenAlias,
(Pyupgrade, "021") => Rule::ReplaceUniversalNewlines,
(Pyupgrade, "022") => Rule::ReplaceStdoutStderr,
(Pyupgrade, "023") => Rule::RewriteCElementTree,
(Pyupgrade, "024") => Rule::OSErrorAlias,
(Pyupgrade, "025") => Rule::RewriteUnicodeLiteral,
(Pyupgrade, "026") => Rule::RewriteMockImport,
(Pyupgrade, "027") => Rule::RewriteListComprehension,
(Pyupgrade, "028") => Rule::RewriteYieldFrom,
(Pyupgrade, "029") => Rule::UnnecessaryBuiltinImport,
(Pyupgrade, "030") => Rule::FormatLiterals,
(Pyupgrade, "031") => Rule::PrintfStringFormatting,
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
// pydocstyle
(Pydocstyle, "100") => Rule::PublicModule,
(Pydocstyle, "101") => Rule::PublicClass,
(Pydocstyle, "102") => Rule::PublicMethod,
(Pydocstyle, "103") => Rule::PublicFunction,
(Pydocstyle, "104") => Rule::PublicPackage,
(Pydocstyle, "105") => Rule::MagicMethod,
(Pydocstyle, "106") => Rule::PublicNestedClass,
(Pydocstyle, "107") => Rule::PublicInit,
(Pydocstyle, "200") => Rule::FitsOnOneLine,
(Pydocstyle, "201") => Rule::NoBlankLineBeforeFunction,
(Pydocstyle, "202") => Rule::NoBlankLineAfterFunction,
(Pydocstyle, "203") => Rule::OneBlankLineBeforeClass,
(Pydocstyle, "204") => Rule::OneBlankLineAfterClass,
(Pydocstyle, "205") => Rule::BlankLineAfterSummary,
(Pydocstyle, "206") => Rule::IndentWithSpaces,
(Pydocstyle, "207") => Rule::NoUnderIndentation,
(Pydocstyle, "208") => Rule::NoOverIndentation,
(Pydocstyle, "209") => Rule::NewLineAfterLastParagraph,
(Pydocstyle, "210") => Rule::NoSurroundingWhitespace,
(Pydocstyle, "211") => Rule::NoBlankLineBeforeClass,
(Pydocstyle, "212") => Rule::MultiLineSummaryFirstLine,
(Pydocstyle, "213") => Rule::MultiLineSummarySecondLine,
(Pydocstyle, "214") => Rule::SectionNotOverIndented,
(Pydocstyle, "215") => Rule::SectionUnderlineNotOverIndented,
(Pydocstyle, "300") => Rule::TripleSingleQuotes,
(Pydocstyle, "301") => Rule::EscapeSequenceInDocstring,
(Pydocstyle, "400") => Rule::EndsInPeriod,
(Pydocstyle, "401") => Rule::NonImperativeMood,
(Pydocstyle, "402") => Rule::NoSignature,
(Pydocstyle, "403") => Rule::FirstLineCapitalized,
(Pydocstyle, "404") => Rule::DocstringStartsWithThis,
(Pydocstyle, "405") => Rule::CapitalizeSectionName,
(Pydocstyle, "406") => Rule::NewLineAfterSectionName,
(Pydocstyle, "407") => Rule::DashedUnderlineAfterSection,
(Pydocstyle, "408") => Rule::SectionUnderlineAfterName,
(Pydocstyle, "409") => Rule::SectionUnderlineMatchesSectionLength,
(Pydocstyle, "410") => Rule::BlankLineAfterSection,
(Pydocstyle, "411") => Rule::BlankLineBeforeSection,
(Pydocstyle, "412") => Rule::NoBlankLinesBetweenHeaderAndContent,
(Pydocstyle, "413") => Rule::BlankLineAfterLastSection,
(Pydocstyle, "414") => Rule::EmptyDocstringSection,
(Pydocstyle, "415") => Rule::EndsInPunctuation,
(Pydocstyle, "416") => Rule::SectionNameEndsInColon,
(Pydocstyle, "417") => Rule::UndocumentedParam,
(Pydocstyle, "418") => Rule::OverloadWithDocstring,
(Pydocstyle, "419") => Rule::EmptyDocstring,
// pep8-naming
(PEP8Naming, "801") => Rule::InvalidClassName,
(PEP8Naming, "802") => Rule::InvalidFunctionName,
(PEP8Naming, "803") => Rule::InvalidArgumentName,
(PEP8Naming, "804") => Rule::InvalidFirstArgumentNameForClassMethod,
(PEP8Naming, "805") => Rule::InvalidFirstArgumentNameForMethod,
(PEP8Naming, "806") => Rule::NonLowercaseVariableInFunction,
(PEP8Naming, "807") => Rule::DunderFunctionName,
(PEP8Naming, "811") => Rule::ConstantImportedAsNonConstant,
(PEP8Naming, "812") => Rule::LowercaseImportedAsNonLowercase,
(PEP8Naming, "813") => Rule::CamelcaseImportedAsLowercase,
(PEP8Naming, "814") => Rule::CamelcaseImportedAsConstant,
(PEP8Naming, "815") => Rule::MixedCaseVariableInClassScope,
(PEP8Naming, "816") => Rule::MixedCaseVariableInGlobalScope,
(PEP8Naming, "817") => Rule::CamelcaseImportedAsAcronym,
(PEP8Naming, "818") => Rule::ErrorSuffixOnExceptionName,
// isort
(Isort, "001") => Rule::UnsortedImports,
(Isort, "002") => Rule::MissingRequiredImport,
// eradicate
(Eradicate, "001") => Rule::CommentedOutCode,
// flake8-bandit
(Flake8Bandit, "101") => Rule::Assert,
(Flake8Bandit, "102") => Rule::ExecBuiltin,
(Flake8Bandit, "103") => Rule::BadFilePermissions,
(Flake8Bandit, "104") => Rule::HardcodedBindAllInterfaces,
(Flake8Bandit, "105") => Rule::HardcodedPasswordString,
(Flake8Bandit, "106") => Rule::HardcodedPasswordFuncArg,
(Flake8Bandit, "107") => Rule::HardcodedPasswordDefault,
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
(Flake8Bandit, "108") => Rule::HardcodedTempFile,
(Flake8Bandit, "110") => Rule::TryExceptPass,
(Flake8Bandit, "112") => Rule::TryExceptContinue,
(Flake8Bandit, "113") => Rule::RequestWithoutTimeout,
(Flake8Bandit, "324") => Rule::HashlibInsecureHashFunction,
(Flake8Bandit, "501") => Rule::RequestWithNoCertValidation,
(Flake8Bandit, "506") => Rule::UnsafeYAMLLoad,
(Flake8Bandit, "508") => Rule::SnmpInsecureVersion,
(Flake8Bandit, "509") => Rule::SnmpWeakCryptography,
(Flake8Bandit, "612") => Rule::LoggingConfigInsecureListen,
(Flake8Bandit, "701") => Rule::Jinja2AutoescapeFalse,
// flake8-boolean-trap
(Flake8BooleanTrap, "001") => Rule::BooleanPositionalArgInFunctionDefinition,
(Flake8BooleanTrap, "002") => Rule::BooleanDefaultValueInFunctionDefinition,
(Flake8BooleanTrap, "003") => Rule::BooleanPositionalValueInFunctionCall,
// flake8-unused-arguments
(Flake8UnusedArguments, "001") => Rule::UnusedFunctionArgument,
(Flake8UnusedArguments, "002") => Rule::UnusedMethodArgument,
(Flake8UnusedArguments, "003") => Rule::UnusedClassMethodArgument,
(Flake8UnusedArguments, "004") => Rule::UnusedStaticMethodArgument,
(Flake8UnusedArguments, "005") => Rule::UnusedLambdaArgument,
// flake8-import-conventions
(Flake8ImportConventions, "001") => Rule::UnconventionalImportAlias,
// flake8-datetimez
(Flake8Datetimez, "001") => Rule::CallDatetimeWithoutTzinfo,
(Flake8Datetimez, "002") => Rule::CallDatetimeToday,
(Flake8Datetimez, "003") => Rule::CallDatetimeUtcnow,
(Flake8Datetimez, "004") => Rule::CallDatetimeUtcfromtimestamp,
(Flake8Datetimez, "005") => Rule::CallDatetimeNowWithoutTzinfo,
(Flake8Datetimez, "006") => Rule::CallDatetimeFromtimestamp,
(Flake8Datetimez, "007") => Rule::CallDatetimeStrptimeWithoutZone,
(Flake8Datetimez, "011") => Rule::CallDateToday,
(Flake8Datetimez, "012") => Rule::CallDateFromtimestamp,
// pygrep-hooks
(PygrepHooks, "001") => Rule::NoEval,
(PygrepHooks, "002") => Rule::DeprecatedLogWarn,
(PygrepHooks, "003") => Rule::BlanketTypeIgnore,
(PygrepHooks, "004") => Rule::BlanketNOQA,
// pandas-vet
(PandasVet, "002") => Rule::UseOfInplaceArgument,
(PandasVet, "003") => Rule::UseOfDotIsNull,
(PandasVet, "004") => Rule::UseOfDotNotNull,
(PandasVet, "007") => Rule::UseOfDotIx,
(PandasVet, "008") => Rule::UseOfDotAt,
(PandasVet, "009") => Rule::UseOfDotIat,
(PandasVet, "010") => Rule::UseOfDotPivotOrUnstack,
(PandasVet, "011") => Rule::UseOfDotValues,
(PandasVet, "012") => Rule::UseOfDotReadTable,
(PandasVet, "013") => Rule::UseOfDotStack,
(PandasVet, "015") => Rule::UseOfPdMerge,
(PandasVet, "901") => Rule::DfIsABadVariableName,
// flake8-errmsg
(Flake8ErrMsg, "101") => Rule::RawStringInException,
(Flake8ErrMsg, "102") => Rule::FStringInException,
(Flake8ErrMsg, "103") => Rule::DotFormatInException,
// flake8-pyi
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
(Flake8PytestStyle, "002") => Rule::FixturePositionalArgs,
(Flake8PytestStyle, "003") => Rule::ExtraneousScopeFunction,
(Flake8PytestStyle, "004") => Rule::MissingFixtureNameUnderscore,
(Flake8PytestStyle, "005") => Rule::IncorrectFixtureNameUnderscore,
(Flake8PytestStyle, "006") => Rule::ParametrizeNamesWrongType,
(Flake8PytestStyle, "007") => Rule::ParametrizeValuesWrongType,
(Flake8PytestStyle, "008") => Rule::PatchWithLambda,
(Flake8PytestStyle, "009") => Rule::UnittestAssertion,
(Flake8PytestStyle, "010") => Rule::RaisesWithoutException,
(Flake8PytestStyle, "011") => Rule::RaisesTooBroad,
(Flake8PytestStyle, "012") => Rule::RaisesWithMultipleStatements,
(Flake8PytestStyle, "013") => Rule::IncorrectPytestImport,
(Flake8PytestStyle, "015") => Rule::AssertAlwaysFalse,
(Flake8PytestStyle, "016") => Rule::FailWithoutMessage,
(Flake8PytestStyle, "017") => Rule::AssertInExcept,
(Flake8PytestStyle, "018") => Rule::CompositeAssertion,
(Flake8PytestStyle, "019") => Rule::FixtureParamWithoutValue,
(Flake8PytestStyle, "020") => Rule::DeprecatedYieldFixture,
(Flake8PytestStyle, "021") => Rule::FixtureFinalizerCallback,
(Flake8PytestStyle, "022") => Rule::UselessYieldFixture,
(Flake8PytestStyle, "023") => Rule::IncorrectMarkParenthesesStyle,
(Flake8PytestStyle, "024") => Rule::UnnecessaryAsyncioMarkOnFixture,
(Flake8PytestStyle, "025") => Rule::ErroneousUseFixturesOnFixture,
(Flake8PytestStyle, "026") => Rule::UseFixturesWithoutParameters,
// flake8-pie
(Flake8Pie, "790") => Rule::UnnecessaryPass,
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::PreferListBuiltin,
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
// flake8-commas
(Flake8Commas, "812") => Rule::TrailingCommaMissing,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTupleProhibited,
(Flake8Commas, "819") => Rule::TrailingCommaProhibited,
// flake8-no-pep420
(Flake8NoPep420, "001") => Rule::ImplicitNamespacePackage,
// flake8-executable
(Flake8Executable, "001") => Rule::ShebangNotExecutable,
(Flake8Executable, "002") => Rule::ShebangMissingExecutableFile,
(Flake8Executable, "003") => Rule::ShebangPython,
(Flake8Executable, "004") => Rule::ShebangWhitespace,
(Flake8Executable, "005") => Rule::ShebangNewline,
// flake8-type-checking
(Flake8TypeChecking, "001") => Rule::TypingOnlyFirstPartyImport,
(Flake8TypeChecking, "002") => Rule::TypingOnlyThirdPartyImport,
(Flake8TypeChecking, "003") => Rule::TypingOnlyStandardLibraryImport,
(Flake8TypeChecking, "004") => Rule::RuntimeImportInTypeCheckingBlock,
(Flake8TypeChecking, "005") => Rule::EmptyTypeCheckingBlock,
// tryceratops
(Tryceratops, "002") => Rule::RaiseVanillaClass,
(Tryceratops, "003") => Rule::RaiseVanillaArgs,
(Tryceratops, "004") => Rule::PreferTypeError,
(Tryceratops, "200") => Rule::ReraiseNoCause,
(Tryceratops, "201") => Rule::VerboseRaise,
(Tryceratops, "300") => Rule::TryConsiderElse,
(Tryceratops, "301") => Rule::RaiseWithinTry,
(Tryceratops, "400") => Rule::ErrorInsteadOfException,
// flake8-use-pathlib
(Flake8UsePathlib, "100") => Rule::PathlibAbspath,
(Flake8UsePathlib, "101") => Rule::PathlibChmod,
(Flake8UsePathlib, "102") => Rule::PathlibMkdir,
(Flake8UsePathlib, "103") => Rule::PathlibMakedirs,
(Flake8UsePathlib, "104") => Rule::PathlibRename,
(Flake8UsePathlib, "105") => Rule::PathlibReplace,
(Flake8UsePathlib, "106") => Rule::PathlibRmdir,
(Flake8UsePathlib, "107") => Rule::PathlibRemove,
(Flake8UsePathlib, "108") => Rule::PathlibUnlink,
(Flake8UsePathlib, "109") => Rule::PathlibGetcwd,
(Flake8UsePathlib, "110") => Rule::PathlibExists,
(Flake8UsePathlib, "111") => Rule::PathlibExpanduser,
(Flake8UsePathlib, "112") => Rule::PathlibIsDir,
(Flake8UsePathlib, "113") => Rule::PathlibIsFile,
(Flake8UsePathlib, "114") => Rule::PathlibIsLink,
(Flake8UsePathlib, "115") => Rule::PathlibReadlink,
(Flake8UsePathlib, "116") => Rule::PathlibStat,
(Flake8UsePathlib, "117") => Rule::PathlibIsAbs,
(Flake8UsePathlib, "118") => Rule::PathlibJoin,
(Flake8UsePathlib, "119") => Rule::PathlibBasename,
(Flake8UsePathlib, "120") => Rule::PathlibDirname,
(Flake8UsePathlib, "121") => Rule::PathlibSamefile,
(Flake8UsePathlib, "122") => Rule::PathlibSplitext,
(Flake8UsePathlib, "123") => Rule::PathlibOpen,
(Flake8UsePathlib, "124") => Rule::PathlibPyPath,
// flake8-logging-format
(Flake8LoggingFormat, "001") => Rule::LoggingStringFormat,
(Flake8LoggingFormat, "002") => Rule::LoggingPercentFormat,
(Flake8LoggingFormat, "003") => Rule::LoggingStringConcat,
(Flake8LoggingFormat, "004") => Rule::LoggingFString,
(Flake8LoggingFormat, "010") => Rule::LoggingWarn,
(Flake8LoggingFormat, "101") => Rule::LoggingExtraAttrClash,
(Flake8LoggingFormat, "201") => Rule::LoggingExcInfo,
(Flake8LoggingFormat, "202") => Rule::LoggingRedundantExcInfo,
// flake8-raise
(Flake8Raise, "102") => Rule::UnnecessaryParenOnRaiseException,
// flake8-self
(Flake8Self, "001") => Rule::PrivateMemberAccess,
// numpy
(Numpy, "001") => Rule::NumpyDeprecatedTypeAlias,
// ruff
(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, "008") => Rule::ModelWithoutDunderStr,
(Flake8Django, "013") => Rule::NonLeadingReceiverDecorator,
_ => return None,
})
}

View File

@@ -6,8 +6,8 @@ use itertools::Itertools;
use super::external_config::ExternalConfig;
use super::plugin::Plugin;
use super::{parser, plugin};
use crate::registry::RuleCodePrefix;
use crate::rule_selector::{prefix_to_selector, RuleSelector};
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
use crate::rules::flake8_pytest_style::types::{
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
};
@@ -23,9 +23,8 @@ use crate::settings::pyproject::Pyproject;
use crate::warn_user;
const DEFAULT_SELECTORS: &[RuleSelector] = &[
prefix_to_selector(RuleCodePrefix::F),
prefix_to_selector(RuleCodePrefix::E),
prefix_to_selector(RuleCodePrefix::W),
RuleSelector::Linter(Linter::Pyflakes),
RuleSelector::Linter(Linter::Pycodestyle),
];
pub fn convert(
@@ -359,13 +358,13 @@ pub fn convert(
options.select = Some(
select
.into_iter()
.sorted_by_key(RuleSelector::short_code)
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
);
options.ignore = Some(
ignore
.into_iter()
.sorted_by_key(RuleSelector::short_code)
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
);
if flake8_annotations != flake8_annotations::settings::Options::default() {
@@ -433,7 +432,7 @@ pub fn convert(
/// plugins.
fn resolve_select(plugins: &[Plugin]) -> HashSet<RuleSelector> {
let mut select: HashSet<_> = DEFAULT_SELECTORS.iter().cloned().collect();
select.extend(plugins.iter().map(Plugin::selector));
select.extend(plugins.iter().map(|p| Linter::from(p).into()));
select
}
@@ -448,7 +447,7 @@ mod tests {
use super::convert;
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
use crate::flake8_to_ruff::ExternalConfig;
use crate::registry::RuleCodePrefix;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
use crate::rules::pydocstyle::settings::Convention;
use crate::rules::{flake8_quotes, pydocstyle};
@@ -463,7 +462,7 @@ mod tests {
.iter()
.cloned()
.chain(plugins)
.sorted_by_key(RuleSelector::short_code)
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
),
..Options::default()
@@ -578,7 +577,7 @@ mod tests {
pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy),
}),
..default_options([RuleCodePrefix::D.into()])
..default_options([Linter::Pydocstyle.into()])
});
assert_eq!(actual, expected);
@@ -602,7 +601,7 @@ mod tests {
docstring_quotes: None,
avoid_escape: None,
}),
..default_options([RuleCodePrefix::Q.into()])
..default_options([Linter::Flake8Quotes.into()])
});
assert_eq!(actual, expected);

View File

@@ -196,7 +196,8 @@ mod tests {
use anyhow::Result;
use super::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
use crate::registry::RuleCodePrefix;
use crate::codes;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
use crate::settings::types::PatternPrefixPair;
@@ -211,19 +212,25 @@ mod tests {
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401");
let expected = vec![RuleCodePrefix::F401.into()];
let expected = vec![codes::Pyflakes::_401.into()];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,");
let expected = vec![RuleCodePrefix::F401.into()];
let expected = vec![codes::Pyflakes::_401.into()];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,E501");
let expected = vec![RuleCodePrefix::F401.into(), RuleCodePrefix::E501.into()];
let expected = vec![
codes::Pyflakes::_401.into(),
codes::Pycodestyle::E501.into(),
];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401, E501");
let expected = vec![RuleCodePrefix::F401.into(), RuleCodePrefix::E501.into()];
let expected = vec![
codes::Pyflakes::_401.into(),
codes::Pycodestyle::E501.into(),
];
assert_eq!(actual, expected);
}
@@ -276,11 +283,11 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "locust/test/*".to_string(),
prefix: RuleCodePrefix::F841.into(),
prefix: codes::Pyflakes::_841.into(),
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: RuleCodePrefix::F841.into(),
prefix: codes::Pyflakes::_841.into(),
},
];
assert_eq!(actual, expected);
@@ -296,23 +303,23 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "t/*".to_string(),
prefix: RuleCodePrefix::D.into(),
prefix: Linter::Pydocstyle.into(),
},
PatternPrefixPair {
pattern: "setup.py".to_string(),
prefix: RuleCodePrefix::D.into(),
prefix: Linter::Pydocstyle.into(),
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: RuleCodePrefix::D.into(),
prefix: Linter::Pydocstyle.into(),
},
PatternPrefixPair {
pattern: "docs/*".to_string(),
prefix: RuleCodePrefix::D.into(),
prefix: Linter::Pydocstyle.into(),
},
PatternPrefixPair {
pattern: "extra/*".to_string(),
prefix: RuleCodePrefix::D.into(),
prefix: Linter::Pydocstyle.into(),
},
];
assert_eq!(actual, expected);
@@ -334,47 +341,47 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "scrapy/__init__.py".to_string(),
prefix: RuleCodePrefix::E402.into(),
prefix: codes::Pycodestyle::E402.into(),
},
PatternPrefixPair {
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
prefix: RuleCodePrefix::F401.into(),
prefix: codes::Pyflakes::_401.into(),
},
PatternPrefixPair {
pattern: "scrapy/http/__init__.py".to_string(),
prefix: RuleCodePrefix::F401.into(),
prefix: codes::Pyflakes::_401.into(),
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: RuleCodePrefix::E402.into(),
prefix: codes::Pycodestyle::E402.into(),
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: RuleCodePrefix::F401.into(),
prefix: codes::Pyflakes::_401.into(),
},
PatternPrefixPair {
pattern: "scrapy/selector/__init__.py".to_string(),
prefix: RuleCodePrefix::F401.into(),
prefix: codes::Pyflakes::_401.into(),
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: RuleCodePrefix::E402.into(),
prefix: codes::Pycodestyle::E402.into(),
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: RuleCodePrefix::F401.into(),
prefix: codes::Pyflakes::_401.into(),
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: RuleCodePrefix::F403.into(),
prefix: codes::Pyflakes::_403.into(),
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: RuleCodePrefix::F405.into(),
prefix: codes::Pyflakes::_405.into(),
},
PatternPrefixPair {
pattern: "tests/test_loader.py".to_string(),
prefix: RuleCodePrefix::E741.into(),
prefix: codes::Pycodestyle::E741.into(),
},
];
assert_eq!(actual, expected);

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::anyhow;
use crate::registry::RuleCodePrefix;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
@@ -131,43 +131,42 @@ impl fmt::Debug for Plugin {
}
}
// TODO(martin): Convert into `impl From<Plugin> for Linter`
impl Plugin {
pub fn selector(&self) -> RuleSelector {
match self {
Plugin::Flake82020 => RuleCodePrefix::YTT.into(),
Plugin::Flake8Annotations => RuleCodePrefix::ANN.into(),
Plugin::Flake8Bandit => RuleCodePrefix::S.into(),
Plugin::Flake8BlindExcept => RuleCodePrefix::BLE.into(),
Plugin::Flake8BooleanTrap => RuleCodePrefix::FBT.into(),
Plugin::Flake8Bugbear => RuleCodePrefix::B.into(),
Plugin::Flake8Builtins => RuleCodePrefix::A.into(),
Plugin::Flake8Commas => RuleCodePrefix::COM.into(),
Plugin::Flake8Comprehensions => RuleCodePrefix::C4.into(),
Plugin::Flake8Datetimez => RuleCodePrefix::DTZ.into(),
Plugin::Flake8Debugger => RuleCodePrefix::T1.into(),
Plugin::Flake8Docstrings => RuleCodePrefix::D.into(),
Plugin::Flake8Eradicate => RuleCodePrefix::ERA.into(),
Plugin::Flake8ErrMsg => RuleCodePrefix::EM.into(),
Plugin::Flake8Executable => RuleCodePrefix::EXE.into(),
Plugin::Flake8ImplicitStrConcat => RuleCodePrefix::ISC.into(),
Plugin::Flake8ImportConventions => RuleCodePrefix::ICN.into(),
Plugin::Flake8NoPep420 => RuleCodePrefix::INP.into(),
Plugin::Flake8Pie => RuleCodePrefix::PIE.into(),
Plugin::Flake8Print => RuleCodePrefix::T2.into(),
Plugin::Flake8PytestStyle => RuleCodePrefix::PT.into(),
Plugin::Flake8Quotes => RuleCodePrefix::Q.into(),
Plugin::Flake8Return => RuleCodePrefix::RET.into(),
Plugin::Flake8Simplify => RuleCodePrefix::SIM.into(),
Plugin::Flake8TidyImports => RuleCodePrefix::TID.into(),
Plugin::Flake8TypeChecking => RuleCodePrefix::TCH.into(),
Plugin::Flake8UnusedArguments => RuleCodePrefix::ARG.into(),
Plugin::Flake8UsePathlib => RuleCodePrefix::PTH.into(),
Plugin::McCabe => RuleCodePrefix::C9.into(),
Plugin::PEP8Naming => RuleCodePrefix::N.into(),
Plugin::PandasVet => RuleCodePrefix::PD.into(),
Plugin::Pyupgrade => RuleCodePrefix::UP.into(),
Plugin::Tryceratops => RuleCodePrefix::TRY.into(),
impl From<&Plugin> for Linter {
fn from(plugin: &Plugin) -> Self {
match plugin {
Plugin::Flake82020 => Linter::Flake82020,
Plugin::Flake8Annotations => Linter::Flake8Annotations,
Plugin::Flake8Bandit => Linter::Flake8Bandit,
Plugin::Flake8BlindExcept => Linter::Flake8BlindExcept,
Plugin::Flake8BooleanTrap => Linter::Flake8BooleanTrap,
Plugin::Flake8Bugbear => Linter::Flake8Bugbear,
Plugin::Flake8Builtins => Linter::Flake8Builtins,
Plugin::Flake8Commas => Linter::Flake8Commas,
Plugin::Flake8Comprehensions => Linter::Flake8Comprehensions,
Plugin::Flake8Datetimez => Linter::Flake8Datetimez,
Plugin::Flake8Debugger => Linter::Flake8Debugger,
Plugin::Flake8Docstrings => Linter::Pydocstyle,
Plugin::Flake8Eradicate => Linter::Eradicate,
Plugin::Flake8ErrMsg => Linter::Flake8ErrMsg,
Plugin::Flake8Executable => Linter::Flake8Executable,
Plugin::Flake8ImplicitStrConcat => Linter::Flake8ImplicitStrConcat,
Plugin::Flake8ImportConventions => Linter::Flake8ImportConventions,
Plugin::Flake8NoPep420 => Linter::Flake8NoPep420,
Plugin::Flake8Pie => Linter::Flake8Pie,
Plugin::Flake8Print => Linter::Flake8Print,
Plugin::Flake8PytestStyle => Linter::Flake8PytestStyle,
Plugin::Flake8Quotes => Linter::Flake8Quotes,
Plugin::Flake8Return => Linter::Flake8Return,
Plugin::Flake8Simplify => Linter::Flake8Simplify,
Plugin::Flake8TidyImports => Linter::Flake8TidyImports,
Plugin::Flake8TypeChecking => Linter::Flake8TypeChecking,
Plugin::Flake8UnusedArguments => Linter::Flake8UnusedArguments,
Plugin::Flake8UsePathlib => Linter::Flake8UsePathlib,
Plugin::McCabe => Linter::McCabe,
Plugin::PEP8Naming => Linter::PEP8Naming,
Plugin::PandasVet => Linter::PandasVet,
Plugin::Pyupgrade => Linter::Pyupgrade,
Plugin::Tryceratops => Linter::Tryceratops,
}
}
}
@@ -334,7 +333,7 @@ pub fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec<Plugin
for selector in selectors {
if selector
.into_iter()
.any(|rule| plugin.selector().into_iter().any(|r| r == rule))
.any(|rule| Linter::from(plugin).into_iter().any(|r| r == rule))
{
return true;
}

View File

@@ -15,6 +15,7 @@ mod ast;
mod autofix;
pub mod cache;
mod checkers;
mod codes;
mod cst;
mod directives;
mod doc_lines;

View File

@@ -66,7 +66,7 @@ impl Serialize for SerializeRuleAsCode {
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.code())
serializer.serialize_str(&self.0.noqa_code().to_string())
}
}

View File

@@ -211,7 +211,7 @@ pub fn check_path(
indexer.commented_lines(),
&directives.noqa_line_for,
settings,
autofix,
error.as_ref().map_or(autofix, |_| flags::Autofix::Disabled),
);
}

View File

@@ -71,9 +71,9 @@ impl Source {
} else {
Location::new(diagnostic.end_location.row() + 1, 0)
};
let source = locator.slice_source_code_range(&Range::new(location, end_location));
let source = locator.slice(&Range::new(location, end_location));
let num_chars_in_range = locator
.slice_source_code_range(&Range::new(diagnostic.location, diagnostic.end_location))
.slice(&Range::new(diagnostic.location, diagnostic.end_location))
.chars()
.count();
Source {

View File

@@ -1,3 +1,4 @@
use std::fmt::{Display, Write};
use std::fs;
use std::path::Path;
@@ -72,7 +73,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
let needle: &str = needle.code();
let needle = needle.noqa_code();
haystack
.iter()
.any(|candidate| needle == get_redirect_target(candidate).unwrap_or(candidate))
@@ -86,7 +87,7 @@ pub fn rule_is_ignored(
locator: &Locator,
) -> bool {
let noqa_lineno = noqa_line_for.get(&lineno).unwrap_or(&lineno);
let line = locator.slice_source_code_range(&Range::new(
let line = locator.slice(&Range::new(
Location::new(*noqa_lineno, 0),
Location::new(noqa_lineno + 1, 0),
));
@@ -205,9 +206,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = rules.iter().map(|r| r.code()).collect();
let suffix = codes.join(", ");
output.push_str(&suffix);
push_codes(&mut output, rules.iter().map(|r| r.noqa_code()));
output.push_str(line_ending);
count += 1;
}
@@ -228,14 +227,14 @@ fn add_noqa_inner(
formatted.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = rules
.iter()
.map(|r| r.code())
.chain(existing.into_iter())
.sorted_unstable()
.collect();
let suffix = codes.join(", ");
formatted.push_str(&suffix);
push_codes(
&mut formatted,
rules
.iter()
.map(|r| r.noqa_code().to_string())
.chain(existing.into_iter().map(ToString::to_string))
.sorted_unstable(),
);
output.push_str(&formatted);
output.push_str(line_ending);
@@ -253,6 +252,17 @@ fn add_noqa_inner(
(count, output)
}
fn push_codes<I: Display>(str: &mut String, codes: impl Iterator<Item = I>) {
let mut first = true;
for code in codes {
if !first {
str.push_str(", ");
}
let _ = write!(str, "{}", code);
first = false;
}
}
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ use std::sync::RwLock;
use anyhow::{anyhow, bail, Result};
use ignore::{DirEntry, WalkBuilder, WalkState};
use itertools::Itertools;
use log::debug;
use path_absolutize::path_dedot;
use rustc_hash::FxHashSet;
@@ -217,7 +218,7 @@ pub fn python_files_in_path(
processor: impl ConfigProcessor,
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
// Normalize every path (e.g., convert from relative to absolute).
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).unique().collect();
// Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default();

View File

@@ -15,6 +15,11 @@ pub(crate) fn get_redirect(code: &str) -> Option<(&'static str, &'static str)> {
static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
HashMap::from_iter([
("C", "C4"),
("C9", "C90"),
("T", "T10"),
("T1", "T10"),
("T2", "T20"),
// TODO(charlie): Remove by 2023-02-01.
("R", "RET"),
("R5", "RET5"),

View File

@@ -1,5 +1,6 @@
use std::str::FromStr;
use itertools::Itertools;
use schemars::_serde_json::Value;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
@@ -8,19 +9,27 @@ use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use crate::registry::{Rule, RuleCodePrefix, RuleIter};
use crate::codes::RuleCodePrefix;
use crate::registry::{Linter, Rule, RuleIter, RuleNamespace};
use crate::rule_redirects::get_redirect;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleSelector {
/// All rules
All,
Linter(Linter),
Prefix {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
},
}
impl From<Linter> for RuleSelector {
fn from(linter: Linter) -> Self {
Self::Linter(linter)
}
}
impl FromStr for RuleSelector {
type Err = ParseError;
@@ -32,9 +41,17 @@ impl FromStr for RuleSelector {
Some((from, target)) => (target, Some(from)),
None => (s, None),
};
let (linter, code) =
Linter::parse_code(s).ok_or_else(|| ParseError::Unknown(s.to_string()))?;
if code.is_empty() {
return Ok(Self::Linter(linter));
}
Ok(Self::Prefix {
prefix: RuleCodePrefix::from_str(s)
.map_err(|_| ParseError::Unknown(s.to_string()))?,
prefix: RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(code.to_string()))?,
redirected_from,
})
}
@@ -50,10 +67,13 @@ pub enum ParseError {
}
impl RuleSelector {
pub fn short_code(&self) -> &'static str {
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
match self {
RuleSelector::All => "ALL",
RuleSelector::Prefix { prefix, .. } => prefix.into(),
RuleSelector::All => ("", "ALL"),
RuleSelector::Prefix { prefix, .. } => {
(prefix.linter().common_prefix(), prefix.short_code())
}
RuleSelector::Linter(l) => (l.common_prefix(), ""),
}
}
}
@@ -63,7 +83,8 @@ impl Serialize for RuleSelector {
where
S: serde::Serializer,
{
serializer.serialize_str(self.short_code())
let (prefix, code) = self.prefix_and_code();
serializer.serialize_str(&format!("{prefix}{code}"))
}
}
@@ -117,14 +138,15 @@ impl IntoIterator for &RuleSelector {
fn into_iter(self) -> Self::IntoIter {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Prefix(prefix.into_iter()),
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.into_iter()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.into_iter()),
}
}
}
pub enum RuleSelectorIter {
All(RuleIter),
Prefix(std::vec::IntoIter<Rule>),
Vec(std::vec::IntoIter<Rule>),
}
impl Iterator for RuleSelectorIter {
@@ -133,7 +155,7 @@ impl Iterator for RuleSelectorIter {
fn next(&mut self) -> Option<Self::Item> {
match self {
RuleSelectorIter::All(iter) => iter.next(),
RuleSelectorIter::Prefix(iter) => iter.next(),
RuleSelectorIter::Vec(iter) => iter.next(),
}
}
}
@@ -160,7 +182,19 @@ impl JsonSchema for RuleSelector {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
std::iter::once("ALL".to_string())
.chain(RuleCodePrefix::iter().map(|s| s.as_ref().to_string()))
.chain(
RuleCodePrefix::iter()
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
format!("{prefix}{code}")
})
.chain(Linter::iter().filter_map(|l| {
let prefix = l.common_prefix();
(!prefix.is_empty()).then(|| prefix.to_string())
}))
.sorted(),
)
.map(Value::String)
.collect(),
),
@@ -173,7 +207,18 @@ impl RuleSelector {
pub(crate) fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::Prefix { prefix, .. } => prefix.specificity(),
RuleSelector::Linter(..) => Specificity::Linter,
RuleSelector::Prefix { prefix, .. } => {
let prefix: &'static str = prefix.short_code();
match prefix.len() {
1 => Specificity::Code1Char,
2 => Specificity::Code2Chars,
3 => Specificity::Code3Chars,
4 => Specificity::Code4Chars,
5 => Specificity::Code5Chars,
_ => panic!("RuleSelector::specificity doesn't yet support codes with so many characters"),
}
}
}
}
}
@@ -188,3 +233,76 @@ pub(crate) enum Specificity {
Code4Chars,
Code5Chars,
}
mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use strum::IntoEnumIterator;
use crate::{
codes::RuleCodePrefix,
registry::{Linter, RuleNamespace},
RuleSelector,
};
#[derive(Clone)]
pub struct RuleSelectorParser;
impl ValueParserFactory for RuleSelector {
type Parser = RuleSelectorParser;
fn value_parser() -> Self::Parser {
RuleSelectorParser
}
}
impl TypedValueParser for RuleSelectorParser {
type Value = RuleSelector;
fn parse_ref(
&self,
_cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let value = value
.to_str()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
value
.parse()
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
Some(Box::new(
std::iter::once(PossibleValue::new("ALL").help("all rules")).chain(
Linter::iter()
.filter_map(|l| {
let prefix = l.common_prefix();
(!prefix.is_empty()).then(|| PossibleValue::new(prefix).help(l.name()))
})
.chain(RuleCodePrefix::iter().map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
let mut rules_iter = p.into_iter();
let rule1 = rules_iter.next();
let rule2 = rules_iter.next();
let value = PossibleValue::new(format!("{prefix}{code}"));
if rule2.is_none() {
let rule1 = rule1.unwrap();
let name: &'static str = rule1.into();
value.help(name)
} else {
value
}
})),
),
))
}
}
}

View File

@@ -15,7 +15,7 @@ mod tests {
#[test_case(Rule::CommentedOutCode, Path::new("ERA001.py"); "ERA001")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("eradicate").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -55,7 +55,7 @@ pub fn commented_out_code(
) -> Option<Diagnostic> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
let line = locator.slice_source_code_range(&Range::new(location, end_location));
let line = locator.slice(&Range::new(location, end_location));
// 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[..]) {

View File

@@ -23,7 +23,7 @@ mod tests {
#[test_case(Rule::SysVersionCmpStr10, Path::new("YTT302.py"); "YTT302")]
#[test_case(Rule::SysVersionSlice1Referenced, Path::new("YTT303.py"); "YTT303")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_2020").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -10,7 +10,7 @@ use crate::source_code::Locator;
/// ANN204
pub fn add_return_none_annotation(locator: &Locator, stmt: &Stmt) -> Result<Fix> {
let range = Range::from_located(stmt);
let contents = locator.slice_source_code_range(&range);
let contents = locator.slice(&range);
// Find the colon (following the `def` keyword).
let mut seen_lpar = false;

View File

@@ -475,9 +475,9 @@ pub fn definition(
// ANN001, ANN401
for arg in args
.args
.posonlyargs
.iter()
.chain(args.posonlyargs.iter())
.chain(args.args.iter())
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
@@ -581,7 +581,7 @@ pub fn definition(
// ANN101, ANN102
if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if let Some(arg) = args.args.first() {
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(&Rule::MissingTypeCls) {

View File

@@ -244,4 +244,15 @@ expression: diagnostics
column: 15
fix: ~
parent: ~
- kind:
MissingTypeSelf:
name: self
location:
row: 108
column: 12
end_location:
row: 108
column: 16
fix: ~
parent: ~

View File

@@ -35,7 +35,7 @@ mod tests {
#[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")]
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"); "S112")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_bandit").join(path).as_path(),
&Settings::for_rule(rule_code),

View File

@@ -17,10 +17,7 @@ impl Violation for HashlibInsecureHashFunction {
#[derive_message_formats]
fn message(&self) -> String {
let HashlibInsecureHashFunction { string } = self;
format!(
"Probable use of insecure hash functions in `hashlib`: \"{}\"",
string.escape_debug()
)
format!("Probable use of insecure hash functions in `hashlib`: `{string}`")
}
}

View File

@@ -14,7 +14,7 @@ mod tests {
#[test_case(Rule::BlindExcept, Path::new("BLE.py"); "BLE001")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_blind_except").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -16,7 +16,7 @@ mod tests {
#[test_case(Rule::BooleanDefaultValueInFunctionDefinition, Path::new("FBT.py"); "FBT002")]
#[test_case(Rule::BooleanPositionalValueInFunctionCall, Path::new("FBT.py"); "FBT003")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_boolean_trap").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -43,7 +43,7 @@ mod tests {
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"); "B904")]
#[test_case(Rule::ZipWithoutExplicitStrict, Path::new("B905.py"); "B905")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_bugbear").join(path).as_path(),
&Settings::for_rule(rule_code),

View File

@@ -3,6 +3,7 @@ use ruff_python::string::is_lower;
use rustpython_parser::ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
@@ -44,15 +45,16 @@ impl<'a> Visitor<'a> for RaiseVisitor {
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. } => {}
StmtKind::If { body, .. }
| StmtKind::While { body, .. }
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, .. } => {
for stmt in body {
self.visit_stmt(stmt);
}
visitor::walk_body(self, body);
}
_ => {}
}
@@ -63,8 +65,6 @@ pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let mut visitor = RaiseVisitor {
diagnostics: vec![],
};
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor::walk_body(&mut visitor, body);
checker.diagnostics.extend(visitor.diagnostics);
}

View File

@@ -36,7 +36,8 @@ pub fn strip_with_multi_characters(checker: &mut Checker, expr: &Expr, func: &Ex
return;
};
if value.len() > 1 && value.chars().unique().count() != value.len() {
let num_chars = value.chars().count();
if num_chars > 1 && num_chars != value.chars().unique().count() {
checker.diagnostics.push(Diagnostic::new(
StripWithMultiCharacters,
Range::from_located(expr),

View File

@@ -19,7 +19,7 @@ impl Violation for UselessContextlibSuppress {
}
}
/// B005
/// B022
pub fn useless_contextlib_suppress(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if args.is_empty()
&& checker.resolve_call_path(func).map_or(false, |call_path| {

View File

@@ -16,7 +16,7 @@ impl Violation for UselessExpression {
}
}
// B018
/// B018
pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) {
for stmt in body {
if let StmtKind::Expr { value } = &stmt.node {

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_bugbear/mod.rs
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
expression: diagnostics
---
- kind:
@@ -62,4 +62,14 @@ expression: diagnostics
column: 18
fix: ~
parent: ~
- kind:
StripWithMultiCharacters: ~
location:
row: 22
column: 0
end_location:
row: 22
column: 13
fix: ~
parent: ~

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_bugbear/mod.rs
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
expression: diagnostics
---
- kind:
@@ -32,4 +32,24 @@ expression: diagnostics
column: 39
fix: ~
parent: ~
- kind:
RaiseWithoutFromInsideExcept: ~
location:
row: 62
column: 8
end_location:
row: 62
column: 35
fix: ~
parent: ~
- kind:
RaiseWithoutFromInsideExcept: ~
location:
row: 64
column: 8
end_location:
row: 64
column: 35
fix: ~
parent: ~

View File

@@ -19,7 +19,7 @@ mod tests {
#[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"); "A002")]
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"); "A003")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_builtins").join(path).as_path(),
&Settings::for_rule(rule_code),
@@ -34,7 +34,7 @@ mod tests {
fn builtins_ignorelist(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"{}_{}_builtins_ignorelist",
rule_code.code(),
rule_code.noqa_code(),
path.to_string_lossy()
);

View File

@@ -8,6 +8,44 @@ use crate::registry::{Diagnostic, DiagnosticKind};
use crate::violation::Violation;
define_violation!(
/// ## What it does
/// Checks for variable (and function) assignments that use the same name
/// as a builtin.
///
/// ## Why is this bad?
/// Reusing a builtin name for the name of a variable increases the
/// difficulty of reading and maintaining the code, and can cause
/// non-obvious errors, as readers may mistake the variable for the
/// builtin and vice versa.
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option.
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python
/// def find_max(list_of_lists):
/// max = 0
/// for flat_list in list_of_lists:
/// for value in flat_list:
/// max = max(max, value) # TypeError: 'int' object is not callable
/// return max
/// ```
///
/// Use instead:
/// ```python
/// def find_max(list_of_lists):
/// result = 0
/// for flat_list in list_of_lists:
/// for value in flat_list:
/// result = max(result, value)
/// return result
/// ```
///
/// * [Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
pub struct BuiltinVariableShadowing {
pub name: String,
}
@@ -21,6 +59,47 @@ impl Violation for BuiltinVariableShadowing {
}
define_violation!(
/// ## What it does
/// Checks for any function arguments that use the same name as a builtin.
///
/// ## Why is this bad?
/// Reusing a builtin name for the name of an argument increases the
/// difficulty of reading and maintaining the code, and can cause
/// non-obvious errors, as readers may mistake the argument for the
/// builtin and vice versa.
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option.
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python
/// def remove_duplicates(list, list2):
/// result = set()
/// for value in list:
/// result.add(value)
/// for value in list2:
/// result.add(value)
/// return list(result) # TypeError: 'list' object is not callable
/// ```
///
/// Use instead:
/// ```python
/// def remove_duplicates(list1, list2):
/// result = set()
/// for value in list1:
/// result.add(value)
/// for value in list2:
/// result.add(value)
/// return list(result)
/// ```
///
/// ## References
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
pub struct BuiltinArgumentShadowing {
pub name: String,
}
@@ -34,6 +113,48 @@ impl Violation for BuiltinArgumentShadowing {
}
define_violation!(
/// ## What it does
/// Checks for any class attributes that use the same name as a builtin.
///
/// ## Why is this bad?
/// Reusing a builtin name for the name of an attribute increases the
/// difficulty of reading and maintaining the code, and can cause
/// non-obvious errors, as readers may mistake the attribute for the
/// builtin and vice versa.
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option, or
/// converted to the appropriate dunder method.
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python
/// class Shadow:
/// def int():
/// return 0
/// ```
///
/// Use instead:
/// ```python
/// class Shadow:
/// def to_int():
/// return 0
/// ```
///
/// Or:
/// ```python
/// class Shadow:
/// # Callable as `int(shadow)`
/// def __int__():
/// return 0
/// ```
///
/// ## References
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
pub struct BuiltinAttributeShadowing {
pub name: String,
}

View File

@@ -35,7 +35,7 @@ pub fn fix_unnecessary_generator_list(
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -82,7 +82,7 @@ pub fn fix_unnecessary_generator_set(
parent: Option<&rustpython_parser::ast::Expr>,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -139,7 +139,7 @@ pub fn fix_unnecessary_generator_dict(
expr: &rustpython_parser::ast::Expr,
parent: Option<&rustpython_parser::ast::Expr>,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -213,7 +213,7 @@ pub fn fix_unnecessary_list_comprehension_set(
) -> Result<Fix> {
// Expr(Call(ListComp)))) ->
// Expr(SetComp)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -257,7 +257,7 @@ pub fn fix_unnecessary_list_comprehension_dict(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -356,7 +356,7 @@ pub fn fix_unnecessary_literal_set(
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(Set)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let mut call = match_call(body)?;
@@ -407,7 +407,7 @@ pub fn fix_unnecessary_literal_dict(
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(Dict)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -480,7 +480,7 @@ pub fn fix_unnecessary_collection_call(
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -593,7 +593,7 @@ pub fn fix_unnecessary_literal_within_tuple_call(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -652,7 +652,7 @@ pub fn fix_unnecessary_literal_within_list_call(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -714,7 +714,7 @@ pub fn fix_unnecessary_list_call(
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
@@ -744,7 +744,7 @@ pub fn fix_unnecessary_call_around_sorted(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let outer_call = match_call(body)?;
@@ -871,7 +871,7 @@ pub fn fix_unnecessary_double_cast_or_process(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let body = match_expr(&mut tree)?;
let mut outer_call = match_call(body)?;
@@ -910,7 +910,7 @@ pub fn fix_unnecessary_comprehension(
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
@@ -986,7 +986,7 @@ pub fn fix_unnecessary_map(
parent: Option<&rustpython_parser::ast::Expr>,
kind: &str,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let module_text = locator.slice(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;

View File

@@ -31,7 +31,7 @@ mod tests {
#[test_case(Rule::UnnecessaryMap, Path::new("C417.py"); "C417")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_comprehensions").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -22,7 +22,7 @@ mod tests {
#[test_case(Rule::CallDateToday, Path::new("DTZ011.py"); "DTZ011")]
#[test_case(Rule::CallDateFromtimestamp, Path::new("DTZ012.py"); "DTZ012")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_datetimez").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -15,7 +15,7 @@ mod tests {
#[test_case(Rule::Debugger, Path::new("T100.py"); "T100")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_debugger").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -12,11 +12,11 @@ mod tests {
use crate::test::test_path;
use crate::{assert_yaml_snapshot, settings};
#[test_case(Rule::ModelStringFieldNullable, Path::new("DJ001.py"); "DJ001")]
#[test_case(Rule::ModelDunderStr, Path::new("DJ008.py"); "DJ008")]
#[test_case(Rule::ReceiverDecoratorChecker, Path::new("DJ013.py"); "DJ013")]
#[test_case(Rule::NullableModelStringField, Path::new("DJ001.py"); "DJ001")]
#[test_case(Rule::ModelWithoutDunderStr, Path::new("DJ008.py"); "DJ008")]
#[test_case(Rule::NonLeadingReceiverDecorator, Path::new("DJ013.py"); "DJ013")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_django").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -1,8 +1,10 @@
mod helpers;
mod model_dunder_str;
mod model_string_field_nullable;
mod receiver_decorator_checker;
pub use model_without_dunder_str::{model_without_dunder_str, ModelWithoutDunderStr};
pub use non_leading_receiver_decorator::{
non_leading_receiver_decorator, NonLeadingReceiverDecorator,
};
pub use nullable_model_string_field::{nullable_model_string_field, NullableModelStringField};
pub use model_dunder_str::{model_dunder_str, ModelDunderStr};
pub use model_string_field_nullable::{model_string_field_nullable, ModelStringFieldNullable};
pub use receiver_decorator_checker::{receiver_decorator_checker, ReceiverDecoratorChecker};
mod helpers;
mod model_without_dunder_str;
mod non_leading_receiver_decorator;
mod nullable_model_string_field;

View File

@@ -40,9 +40,9 @@ define_violation!(
/// def __str__(self):
/// return f"{self.field}"
/// ```
pub struct ModelDunderStr;
pub struct ModelWithoutDunderStr;
);
impl Violation for ModelDunderStr {
impl Violation for ModelWithoutDunderStr {
#[derive_message_formats]
fn message(&self) -> String {
format!("Model does not define `__str__` method")
@@ -50,7 +50,7 @@ impl Violation for ModelDunderStr {
}
/// DJ008
pub fn model_dunder_str(
pub fn model_without_dunder_str(
checker: &Checker,
bases: &[Expr],
body: &[Stmt],
@@ -61,7 +61,7 @@ pub fn model_dunder_str(
}
if !has_dunder_method(body) {
return Some(Diagnostic::new(
ModelDunderStr,
ModelWithoutDunderStr,
Range::from_located(class_location),
));
}

View File

@@ -38,9 +38,9 @@ define_violation!(
/// def my_handler(sender, instance, created, **kwargs):
/// pass
/// ```
pub struct ReceiverDecoratorChecker;
pub struct NonLeadingReceiverDecorator;
);
impl Violation for ReceiverDecoratorChecker {
impl Violation for NonLeadingReceiverDecorator {
#[derive_message_formats]
fn message(&self) -> String {
format!("`@receiver` decorator must be on top of all the other decorators")
@@ -48,28 +48,33 @@ impl Violation for ReceiverDecoratorChecker {
}
/// DJ013
pub fn receiver_decorator_checker<'a, F>(
pub fn non_leading_receiver_decorator<'a, F>(
decorator_list: &'a [Expr],
resolve_call_path: F,
) -> Option<Diagnostic>
) -> Vec<Diagnostic>
where
F: Fn(&'a Expr) -> Option<CallPath<'a>>,
{
let mut diagnostics = vec![];
let mut seen_receiver = false;
for (i, decorator) in decorator_list.iter().enumerate() {
if i == 0 {
continue;
}
let ExprKind::Call{ func, ..} = &decorator.node else {
continue;
let is_receiver = match &decorator.node {
ExprKind::Call { func, .. } => resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["django", "dispatch", "receiver"]
}),
_ => false,
};
if resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["django", "dispatch", "receiver"]
}) {
return Some(Diagnostic::new(
ReceiverDecoratorChecker,
if i > 0 && is_receiver && !seen_receiver {
diagnostics.push(Diagnostic::new(
NonLeadingReceiverDecorator,
Range::from_located(decorator),
));
}
if !is_receiver && seen_receiver {
seen_receiver = false;
} else if is_receiver {
seen_receiver = true;
}
}
None
diagnostics
}

View File

@@ -1,11 +1,14 @@
use super::helpers;
use rustpython_parser::ast::Constant::Bool;
use rustpython_parser::ast::{Expr, ExprKind, Stmt, StmtKind};
use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Constant::Bool;
use rustpython_parser::ast::{Expr, ExprKind, Stmt, StmtKind};
use super::helpers;
define_violation!(
/// ## What it does
@@ -36,14 +39,14 @@ define_violation!(
/// class MyModel(models.Model):
/// field = models.CharField(max_length=255, default="")
/// ```
pub struct ModelStringFieldNullable {
pub struct NullableModelStringField {
pub field_name: String,
}
);
impl Violation for ModelStringFieldNullable {
impl Violation for NullableModelStringField {
#[derive_message_formats]
fn message(&self) -> String {
let ModelStringFieldNullable { field_name } = self;
let NullableModelStringField { field_name } = self;
format!("Avoid using `null=True` on string-based fields such as {field_name}")
}
}
@@ -58,23 +61,15 @@ const NOT_NULL_TRUE_FIELDS: [&str; 6] = [
];
/// DJ001
pub fn model_string_field_nullable(
checker: &Checker,
bases: &[Expr],
body: &[Stmt],
) -> Vec<Diagnostic> {
if !bases.iter().any(|base| helpers::is_model(checker, base)) {
return vec![];
}
pub fn nullable_model_string_field(checker: &Checker, body: &[Stmt]) -> Vec<Diagnostic> {
let mut errors = Vec::new();
for statement in body.iter() {
let StmtKind::Assign {value, ..} = &statement.node else {
continue
};
if let Some(field_name) = check_nullable_field(checker, value) {
if let Some(field_name) = is_nullable_field(checker, value) {
errors.push(Diagnostic::new(
ModelStringFieldNullable {
NullableModelStringField {
field_name: field_name.to_string(),
},
Range::from_located(value),
@@ -84,7 +79,7 @@ pub fn model_string_field_nullable(
errors
}
fn check_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
let ExprKind::Call {func, keywords, ..} = &value.node else {
return None;
};

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_django/mod.rs
expression: diagnostics
---
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: CharField
location:
row: 7
@@ -14,7 +14,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: TextField
location:
row: 8
@@ -25,7 +25,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: SlugField
location:
row: 9
@@ -36,7 +36,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: EmailField
location:
row: 10
@@ -47,7 +47,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: FilePathField
location:
row: 11
@@ -58,7 +58,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: URLField
location:
row: 12
@@ -69,7 +69,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: CharField
location:
row: 16
@@ -80,7 +80,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: CharField
location:
row: 17
@@ -91,7 +91,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: SlugField
location:
row: 18
@@ -102,7 +102,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: EmailField
location:
row: 19
@@ -113,7 +113,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: FilePathField
location:
row: 20
@@ -124,7 +124,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelStringFieldNullable:
NullableModelStringField:
field_name: URLField
location:
row: 21
@@ -134,4 +134,70 @@ expression: diagnostics
column: 57
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: CharField
location:
row: 25
column: 16
end_location:
row: 25
column: 64
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: CharField
location:
row: 26
column: 16
end_location:
row: 26
column: 56
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: SlugField
location:
row: 27
column: 16
end_location:
row: 27
column: 59
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: EmailField
location:
row: 28
column: 17
end_location:
row: 28
column: 61
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: FilePathField
location:
row: 29
column: 20
end_location:
row: 29
column: 67
fix: ~
parent: ~
- kind:
NullableModelStringField:
field_name: URLField
location:
row: 30
column: 15
end_location:
row: 30
column: 57
fix: ~
parent: ~

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_django/mod.rs
expression: diagnostics
---
- kind:
ModelDunderStr: ~
ModelWithoutDunderStr: ~
location:
row: 6
column: 0
@@ -13,7 +13,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelDunderStr: ~
ModelWithoutDunderStr: ~
location:
row: 21
column: 0
@@ -23,7 +23,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
ModelDunderStr: ~
ModelWithoutDunderStr: ~
location:
row: 36
column: 0

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_django/mod.rs
expression: diagnostics
---
- kind:
ReceiverDecoratorChecker: ~
NonLeadingReceiverDecorator: ~
location:
row: 15
column: 1
@@ -12,4 +12,14 @@ expression: diagnostics
column: 35
fix: ~
parent: ~
- kind:
NonLeadingReceiverDecorator: ~
location:
row: 35
column: 1
end_location:
row: 35
column: 35
fix: ~
parent: ~

View File

@@ -7,6 +7,43 @@ use crate::registry::{Diagnostic, Rule};
use crate::violation::Violation;
define_violation!(
/// ## What it does
/// Checks for the use of string literals in exception constructors.
///
/// ## Why is this bad?
/// Python includes the `raise` in the default traceback (and formatters
/// like Rich and IPython do too).
///
/// By using a string literal, the error message will be duplicated in the
/// traceback, which can make the traceback less readable.
///
/// ## Example
/// Given:
/// ```python
/// raise RuntimeError("'Some value' is incorrect")
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError("Some value is incorrect")
/// RuntimeError: 'Some value' is incorrect
/// ```
///
/// Instead, assign the string to a variable:
/// ```python
/// msg = "'Some value' is incorrect"
/// raise RuntimeError(msg)
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
/// ```
pub struct RawStringInException;
);
impl Violation for RawStringInException {
@@ -17,6 +54,45 @@ impl Violation for RawStringInException {
}
define_violation!(
/// ## What it does
/// Checks for the use of f-strings in exception constructors.
///
/// ## Why is this bad?
/// Python includes the `raise` in the default traceback (and formatters
/// like Rich and IPython do too).
///
/// By using an f-string, the error message will be duplicated in the
/// traceback, which can make the traceback less readable.
///
/// ## Example
/// Given:
/// ```python
/// sub = "Some value"
/// raise RuntimeError(f"{sub!r} is incorrect")
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError(f"{sub!r} is incorrect")
/// RuntimeError: 'Some value' is incorrect
/// ```
///
/// Instead, assign the string to a variable:
/// ```python
/// sub = "Some value"
/// msg = f"{sub!r} is incorrect"
/// raise RuntimeError(msg)
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
/// ```
pub struct FStringInException;
);
impl Violation for FStringInException {
@@ -27,6 +103,46 @@ impl Violation for FStringInException {
}
define_violation!(
/// ## What it does
/// Checks for the use of `.format` calls on string literals in exception
/// constructors.
///
/// ## Why is this bad?
/// Python includes the `raise` in the default traceback (and formatters
/// like Rich and IPython do too).
///
/// By using a `.format` call, the error message will be duplicated in the
/// traceback, which can make the traceback less readable.
///
/// ## Example
/// Given:
/// ```python
/// sub = "Some value"
/// raise RuntimeError("'{}' is incorrect".format(sub))
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError("'{}' is incorrect".format(sub))
/// RuntimeError: 'Some value' is incorrect
/// ```
///
/// Instead, assign the string to a variable:
/// ```python
/// sub = "Some value"
/// msg = "'{}' is incorrect".format(sub)
/// raise RuntimeError(msg)
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
/// ```
pub struct DotFormatInException;
);
impl Violation for DotFormatInException {

View File

@@ -12,7 +12,7 @@ define_violation!(
impl Violation for ShebangPython {
#[derive_message_formats]
fn message(&self) -> String {
format!("Shebang should contain \"python\"")
format!("Shebang should contain `python`")
}
}

View File

@@ -17,7 +17,7 @@ mod tests {
#[test_case(Rule::MultiLineImplicitStringConcatenation, Path::new("ISC.py"); "ISC002")]
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"); "ISC003")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_implicit_str_concat").join(path).as_path(),
&settings::Settings::for_rule(rule_code),
@@ -30,7 +30,11 @@ mod tests {
#[test_case(Rule::MultiLineImplicitStringConcatenation, Path::new("ISC.py"); "ISC002")]
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"); "ISC003")]
fn multiline(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("multiline_{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!(
"multiline_{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_implicit_str_concat").join(path).as_path(),
&settings::Settings {

View File

@@ -25,7 +25,6 @@ define_violation!(
/// the absence of the `__init__.py` file is probably an oversight.
///
/// ## Options
///
/// * `namespace-packages`
pub struct ImplicitNamespacePackage {
pub filename: String,

View File

@@ -20,7 +20,7 @@ mod tests {
#[test_case(Rule::PreferListBuiltin, Path::new("PIE807.py"); "PIE807")]
#[test_case(Rule::PreferUniqueEnums, Path::new("PIE796.py"); "PIE796")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_pie").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -15,7 +15,7 @@ mod tests {
#[test_case(Rule::PrintFound, Path::new("T201.py"); "T201")]
#[test_case(Rule::PPrintFound, Path::new("T203.py"); "T203")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_print").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -19,7 +19,7 @@ mod tests {
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_pyi").join(path).as_path(),
&settings::Settings::for_rule(rule_code),

View File

@@ -55,7 +55,7 @@ impl Violation for PrefixTypeParams {
#[derive_message_formats]
fn message(&self) -> String {
let PrefixTypeParams { kind } = self;
format!("Name of private `{kind}` must start with _")
format!("Name of private `{kind}` must start with `_`")
}
}

View File

@@ -36,13 +36,13 @@ define_violation!(
/// ```
///
/// ## References
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
/// * [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
pub struct UnrecognizedPlatformCheck;
);
impl Violation for UnrecognizedPlatformCheck {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unrecognized sys.platform check")
format!("Unrecognized `sys.platform` check")
}
}
@@ -72,7 +72,7 @@ define_violation!(
/// ```
///
/// ## References
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
/// * [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
pub struct UnrecognizedPlatformName {
pub platform: String,
}

View File

@@ -21,7 +21,6 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.inline-quotes`
///
/// ## Example
@@ -67,7 +66,6 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.multiline-quotes`
///
/// ## Example
@@ -116,7 +114,6 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.docstring-quotes`
///
/// ## Example
@@ -266,7 +263,7 @@ fn docstring(
) -> Option<Diagnostic> {
let quotes_settings = &settings.flake8_quotes;
let text = locator.slice_source_code_range(&Range::new(start, end));
let text = locator.slice(&Range::new(start, end));
let trivia: Trivia = text.into();
if trivia
@@ -313,7 +310,7 @@ fn strings(
let trivia = sequence
.iter()
.map(|(start, end)| {
let text = locator.slice_source_code_range(&Range::new(*start, *end));
let text = locator.slice(&Range::new(*start, *end));
let trivia: Trivia = text.into();
trivia
})

View File

@@ -25,7 +25,7 @@ mod tests {
#[test_case(Rule::SuperfluousElseContinue, Path::new("RET507.py"); "RET507")]
#[test_case(Rule::SuperfluousElseBreak, Path::new("RET508.py"); "RET508")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_return").join(path).as_path(),
&Settings::for_rule(rule_code),

View File

@@ -423,13 +423,7 @@ fn superfluous_elif(checker: &mut Checker, stack: &Stack) -> bool {
/// RET505, RET506, RET507, RET508
fn superfluous_else(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.ifs {
let StmtKind::If { orelse, .. } = &stmt.node else {
continue;
};
if orelse.is_empty() {
continue;
}
for stmt in &stack.elses {
if superfluous_else_node(checker, stmt, Branch::Else) {
return true;
}

View File

@@ -8,7 +8,7 @@ use crate::ast::visitor::Visitor;
pub struct Stack<'a> {
pub returns: Vec<(&'a Stmt, Option<&'a Expr>)>,
pub yields: Vec<&'a Expr>,
pub ifs: Vec<&'a Stmt>,
pub elses: Vec<&'a Stmt>,
pub elifs: Vec<&'a Stmt>,
pub refs: FxHashMap<&'a str, Vec<Location>>,
pub non_locals: FxHashSet<&'a str>,
@@ -20,6 +20,7 @@ pub struct Stack<'a> {
#[derive(Default)]
pub struct ReturnVisitor<'a> {
pub stack: Stack<'a>,
parents: Vec<&'a Stmt>,
}
impl<'a> ReturnVisitor<'a> {
@@ -72,16 +73,37 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
self.stack
.returns
.push((stmt, value.as_ref().map(|expr| &**expr)));
self.parents.push(stmt);
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
StmtKind::If { orelse, .. } => {
if orelse.len() == 1 && matches!(orelse.first().unwrap().node, StmtKind::If { .. })
{
self.stack.elifs.push(stmt);
} else {
self.stack.ifs.push(stmt);
let is_elif_arm = self.parents.iter().any(|parent| {
if let StmtKind::If { orelse, .. } = &parent.node {
orelse.len() == 1 && &orelse[0] == stmt
} else {
false
}
});
if !is_elif_arm {
let has_elif = orelse.len() == 1
&& matches!(orelse.first().unwrap().node, StmtKind::If { .. });
let has_else = !orelse.is_empty();
if has_elif {
// `stmt` is an `if` block followed by an `elif` clause.
self.stack.elifs.push(stmt);
} else if has_else {
// `stmt` is an `if` block followed by an `else` clause.
self.stack.elses.push(stmt);
}
}
self.parents.push(stmt);
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
StmtKind::Assign { targets, value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
@@ -109,16 +131,24 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
self.stack
.loops
.push((stmt.location, stmt.end_location.unwrap()));
self.parents.push(stmt);
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
StmtKind::Try { .. } => {
self.stack
.tries
.push((stmt.location, stmt.end_location.unwrap()));
self.parents.push(stmt);
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
_ => {
self.parents.push(stmt);
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
}
}

View File

@@ -3,12 +3,48 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::helpers::collect_call_path;
use crate::ast::types::Range;
use crate::ast::types::{BindingKind, Range, ScopeKind};
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
/// ## What it does
/// Checks for accesses on "private" class members.
///
/// ## Why is this bad?
/// In Python, the convention is such that class members that are prefixed
/// with a single underscore, or prefixed but not suffixed with a double
/// underscore, are considered private and intended for internal use.
///
/// Using such "private" members is considered a misuse of the class, as
/// there are no guarantees that the member will be present in future
/// versions, that it will have the same type, or that it will have the same
/// behavior. Instead, use the class's public interface.
///
/// ## Example
/// ```python
/// class Class:
/// def __init__(self):
/// self._private_member = "..."
///
/// var = Class()
/// print(var._private_member)
/// ```
///
/// Use instead:
/// ```python
/// class Class:
/// def __init__(self):
/// self.public_member = "..."
///
/// var = Class()
/// print(var.public_member)
/// ```
///
/// ## References
/// * [_What is the meaning of single or double underscores before an object name?_](https://stackoverflow.com/questions/1301346/what-is-the-meaning-of-single-and-double-underscore-before-an-object-name)
/// ```
pub struct PrivateMemberAccess {
pub access: String,
}
@@ -24,13 +60,17 @@ impl Violation for PrivateMemberAccess {
/// SLF001
pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if !attr.ends_with("__") && (attr.starts_with('_') || attr.starts_with("__")) {
if (attr.starts_with("__") && !attr.ends_with("__"))
|| (attr.starts_with('_') && !attr.starts_with("__"))
{
if let ExprKind::Call { func, .. } = &value.node {
// Ignore `super()` calls.
let call_path = collect_call_path(func);
if call_path.as_slice() == ["super"] {
return;
}
} else {
// Ignore `self` and `cls` accesses.
let call_path = collect_call_path(value);
if call_path.as_slice() == ["self"]
|| call_path.as_slice() == ["cls"]
@@ -38,6 +78,32 @@ pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
{
return;
}
// Ignore accesses on class members from _within_ the class.
if checker
.scopes
.iter()
.rev()
.find_map(|scope| match &scope.kind {
ScopeKind::Class(class_def) => Some(class_def),
_ => None,
})
.map_or(false, |class_def| {
if call_path.as_slice() == [class_def.name] {
checker
.find_binding(class_def.name)
.map_or(false, |binding| {
// TODO(charlie): Could the name ever be bound to a _different_
// class here?
matches!(binding.kind, BindingKind::ClassDefinition)
})
} else {
false
}
})
{
return;
}
}
checker.diagnostics.push(Diagnostic::new(

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