Compare commits
216 Commits
v0.0.280
...
collect_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f2979ed66 | ||
|
|
fe97a2a302 | ||
|
|
a48d16e025 | ||
|
|
8a5bc93fdd | ||
|
|
6da527170f | ||
|
|
8cddb6c08d | ||
|
|
b8ca220eeb | ||
|
|
1d8759d5df | ||
|
|
d3aa8b4ee0 | ||
|
|
9ae498595c | ||
|
|
5f225b18ab | ||
|
|
8276b26480 | ||
|
|
1705fcef36 | ||
|
|
51ff98f9e9 | ||
|
|
b3f3529499 | ||
|
|
2fa508793f | ||
|
|
718e3945e3 | ||
|
|
c75e8a8dab | ||
|
|
74e734e962 | ||
|
|
0e18abcf95 | ||
|
|
7c8bcede5b | ||
|
|
30c2e9430e | ||
|
|
b6f0316d55 | ||
|
|
9f3567dea6 | ||
|
|
9e2bbf4beb | ||
|
|
d7627c398c | ||
|
|
23e527e386 | ||
|
|
a15b0a9102 | ||
|
|
d40597a266 | ||
|
|
82410524d9 | ||
|
|
5b2e973fa5 | ||
|
|
1a60d1e3c6 | ||
|
|
9425ed72a0 | ||
|
|
9f38dbd06e | ||
|
|
7c5791fb77 | ||
|
|
c362ea7fd4 | ||
|
|
ec8fad5b02 | ||
|
|
bcc41ba062 | ||
|
|
556abf4bd3 | ||
|
|
23b8fc4366 | ||
|
|
fd40864924 | ||
|
|
041946fb64 | ||
|
|
8a0f844642 | ||
|
|
8c40886f87 | ||
|
|
4c53bfe896 | ||
|
|
b095b7204b | ||
|
|
981e64f82b | ||
|
|
0d62ad2480 | ||
|
|
b4f224ecea | ||
|
|
7842c82a0a | ||
|
|
9c708d8fc1 | ||
|
|
adc8bb7821 | ||
|
|
a82eb9544c | ||
|
|
5e41f2fc7d | ||
|
|
1df7e9831b | ||
|
|
2e1754e5fc | ||
|
|
67b88803d8 | ||
|
|
ed45fcb1f7 | ||
|
|
adf227b8a9 | ||
|
|
debfca3a11 | ||
|
|
83fe103d6e | ||
|
|
e08f873077 | ||
|
|
928ab63a64 | ||
|
|
b68f76f0d9 | ||
|
|
1a85953129 | ||
|
|
743118ae9a | ||
|
|
0753017cf1 | ||
|
|
29fb655e04 | ||
|
|
f45e8645d7 | ||
|
|
7c7231db2e | ||
|
|
4ad5903ef6 | ||
|
|
c6986ac95d | ||
|
|
ecfdd8d58b | ||
|
|
07468f8be9 | ||
|
|
ba990b676f | ||
|
|
44a8d1c644 | ||
|
|
88b984e885 | ||
|
|
bf584c6d74 | ||
|
|
6ea3c178fd | ||
|
|
764d35667f | ||
|
|
ff9ebbaa5f | ||
|
|
38b5726948 | ||
|
|
615337a54d | ||
|
|
6ee5cb37c0 | ||
|
|
0fddb31235 | ||
|
|
a7aa3caaae | ||
|
|
e52b636da0 | ||
|
|
9063f4524d | ||
|
|
dbd60b2cf5 | ||
|
|
7eb2ba47cc | ||
|
|
cb34e6d322 | ||
|
|
0274de1fff | ||
|
|
a540933bc9 | ||
|
|
311a1f9ec4 | ||
|
|
b95fc6d162 | ||
|
|
de898c52eb | ||
|
|
76741cac77 | ||
|
|
5d9814d84d | ||
|
|
1d7ad30188 | ||
|
|
4231ed2fc3 | ||
|
|
badbfb2d3e | ||
|
|
44bdf20221 | ||
|
|
3c99fbf808 | ||
|
|
4802c7c7d8 | ||
|
|
646ff6497c | ||
|
|
e0d5c7564f | ||
|
|
7838d8c8af | ||
|
|
047c211837 | ||
|
|
134d447d4c | ||
|
|
cd4147423c | ||
|
|
d15436458f | ||
|
|
0bc3edf6c9 | ||
|
|
3d54d31cd9 | ||
|
|
1418ee62f8 | ||
|
|
bf987f80f4 | ||
|
|
bb08eea5cc | ||
|
|
d16216a2c2 | ||
|
|
0853004f41 | ||
|
|
fb5bbe30c7 | ||
|
|
dd706c7a35 | ||
|
|
e15b9c5572 | ||
|
|
0638a26347 | ||
|
|
2a65e6fc38 | ||
|
|
13af91299d | ||
|
|
d317af442f | ||
|
|
6bf6646c5d | ||
|
|
9574ff3dc7 | ||
|
|
06d9ff9577 | ||
|
|
40f54375cb | ||
|
|
86539c1fc5 | ||
|
|
d04367a042 | ||
|
|
ffdd653c54 | ||
|
|
132f07c27b | ||
|
|
c0dbcb3434 | ||
|
|
788643f718 | ||
|
|
64a186272f | ||
|
|
8113615534 | ||
|
|
ecf4058e52 | ||
|
|
2d2673f613 | ||
|
|
564304eba2 | ||
|
|
5b8fc753ec | ||
|
|
13f9a16e33 | ||
|
|
2cf00fee96 | ||
|
|
99127243f4 | ||
|
|
77396c6f92 | ||
|
|
16e1737d1b | ||
|
|
025fa4eba8 | ||
|
|
1fdadee59c | ||
|
|
c8ee357613 | ||
|
|
96d2ca0bda | ||
|
|
62f821daaa | ||
|
|
9dfe484472 | ||
|
|
da33c26238 | ||
|
|
8c80bfa7da | ||
|
|
389fe13c93 | ||
|
|
3000a47fe8 | ||
|
|
cbf6085375 | ||
|
|
9171bd4c28 | ||
|
|
f5c69c1b34 | ||
|
|
5f63b8bfb8 | ||
|
|
c996b614fe | ||
|
|
670db1db4b | ||
|
|
242cbd966d | ||
|
|
e7f228f781 | ||
|
|
51d8fc1f30 | ||
|
|
ed72c027a3 | ||
|
|
b7e7346081 | ||
|
|
d35b5248ea | ||
|
|
c535e10fff | ||
|
|
c3ecdb8783 | ||
|
|
242df67cbf | ||
|
|
776d598738 | ||
|
|
7f3797185c | ||
|
|
a9f535997d | ||
|
|
fdb3c8852f | ||
|
|
42d969f19f | ||
|
|
62ffc773de | ||
|
|
6feb3fcc1b | ||
|
|
8eadacda33 | ||
|
|
8a7dcb794b | ||
|
|
dfa81b6fe0 | ||
|
|
33196f1859 | ||
|
|
0d94337b96 | ||
|
|
f9726af4ef | ||
|
|
727153cf45 | ||
|
|
574c0e0105 | ||
|
|
700c816fd5 | ||
|
|
3b56f6d616 | ||
|
|
110fa804ff | ||
|
|
2b9c22de0f | ||
|
|
51ebff7e41 | ||
|
|
742f615792 | ||
|
|
95e6258d5d | ||
|
|
5dbb4dd823 | ||
|
|
46f8961292 | ||
|
|
f886b58c92 | ||
|
|
057faabcdd | ||
|
|
0bb175f7f6 | ||
|
|
4b2ec7d562 | ||
|
|
4aac801277 | ||
|
|
45a24912a6 | ||
|
|
3914fcb7ca | ||
|
|
6d58b773b1 | ||
|
|
e7f5121922 | ||
|
|
1776cbd2e2 | ||
|
|
71f1643eda | ||
|
|
74dc137b30 | ||
|
|
97e31cad2f | ||
|
|
ed7d2b8a3d | ||
|
|
c7e4c58181 | ||
|
|
6ff566f2c1 | ||
|
|
32773e8309 | ||
|
|
050f5953f8 | ||
|
|
aba340a177 | ||
|
|
33657d3a1c | ||
|
|
45318d08b7 |
1
.github/release.yml
vendored
1
.github/release.yml
vendored
@@ -4,6 +4,7 @@ changelog:
|
||||
labels:
|
||||
- internal
|
||||
- documentation
|
||||
- formatter
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
|
||||
14
.github/workflows/benchmark.yaml
vendored
14
.github/workflows/benchmark.yaml
vendored
@@ -3,12 +3,12 @@ name: Benchmark
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'rust-toolchain'
|
||||
- 'crates/**'
|
||||
- '!crates/ruff_dev'
|
||||
- '!crates/ruff_shrinking'
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "rust-toolchain"
|
||||
- "crates/**"
|
||||
- "!crates/ruff_dev"
|
||||
- "!crates/ruff_shrinking"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
name: "Run | ${{ matrix.os }}"
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
|
||||
25
.github/workflows/ci.yaml
vendored
25
.github/workflows/ci.yaml
vendored
@@ -2,7 +2,7 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -16,7 +16,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.11" # to build abi3 wheels
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/check_ecosystem.py
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -50,7 +51,11 @@ jobs:
|
||||
- crates/ruff_formatter/**
|
||||
- crates/ruff_python_trivia/**
|
||||
- crates/ruff_python_ast/**
|
||||
|
||||
- crates/ruff_source_file/**
|
||||
- crates/ruff_python_index/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -79,7 +84,7 @@ jobs:
|
||||
cargo-test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "cargo test | ${{ matrix.os }}"
|
||||
steps:
|
||||
@@ -231,7 +236,6 @@ jobs:
|
||||
- name: "Run cargo-udeps"
|
||||
run: cargo +nightly-2023-06-08 udeps
|
||||
|
||||
|
||||
python-package:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -319,8 +323,8 @@ jobs:
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
|
||||
check-formatter-stability:
|
||||
name: "Check formatter stability"
|
||||
check-formatter-ecosystem:
|
||||
name: "Formatter ecosystem and progress checks"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true'
|
||||
@@ -330,7 +334,12 @@ jobs:
|
||||
run: rustup show
|
||||
- name: "Cache rust"
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: "Formatter progress"
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
# CPython is not black formatted, so we run only the stability check
|
||||
- name: "Clone CPython 3.10"
|
||||
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
|
||||
- name: "Check stability"
|
||||
- name: "Check CPython stability"
|
||||
run: cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -3,7 +3,7 @@ name: mkdocs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
mkdocs:
|
||||
|
||||
6
.github/workflows/flake8-to-ruff.yaml
vendored
6
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x64, x86 ]
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x86_64, i686 ]
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ aarch64, armv7, s390x, ppc64le, ppc64 ]
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -3,7 +3,7 @@ name: "[Playground] Release"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
4
.github/workflows/pr-comment.yaml
vendored
4
.github/workflows/pr-comment.yaml
vendored
@@ -2,8 +2,8 @@ name: PR Check Comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ CI, Benchmark ]
|
||||
types: [ completed ]
|
||||
workflows: [CI, Benchmark]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_run_id:
|
||||
|
||||
@@ -42,13 +42,13 @@ repos:
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --
|
||||
language: system
|
||||
types: [ rust ]
|
||||
types: [rust]
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
- id: ruff
|
||||
name: ruff
|
||||
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
|
||||
language: system
|
||||
types_or: [ python, pyi ]
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
exclude: |
|
||||
(?x)^(
|
||||
@@ -62,5 +62,12 @@ repos:
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
ci:
|
||||
skip: [ cargo-fmt, dev-generate-all ]
|
||||
skip: [cargo-fmt, dev-generate-all]
|
||||
|
||||
159
CONTRIBUTING.md
159
CONTRIBUTING.md
@@ -69,9 +69,16 @@ and pre-commit to run some validation checks:
|
||||
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
||||
```
|
||||
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run Ruff locally with:
|
||||
After cloning the repository, run Ruff locally from the repository root with:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check /path/to/file.py --no-cache
|
||||
@@ -122,9 +129,9 @@ At time of writing, the repository includes the following crates:
|
||||
intermediate representation. The backend for `ruff_python_formatter`.
|
||||
- `crates/ruff_index`: library crate inspired by `rustc_index`.
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities. Note
|
||||
that the AST schema itself is defined in the
|
||||
[rustpython-ast](https://github.com/astral-sh/RustPython-Parser) crate.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||
line length.
|
||||
@@ -135,8 +142,7 @@ At time of writing, the repository includes the following crates:
|
||||
the names of all built-in exceptions and which standard library types are immutable.
|
||||
- `crates/ruff_python_trivia`: library crate containing Python-specific trivia utilities (e.g.,
|
||||
for analyzing indentation, newlines, etc.).
|
||||
- `crates/ruff_rustpython`: library crate containing `RustPython`-specific utilities.
|
||||
- `crates/ruff_textwrap`: library crate to indent and dedent Python source code.
|
||||
- `crates/ruff_python_parser`: library crate containing the Python parser.
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module. Powers the
|
||||
[Ruff Playground](https://play.ruff.rs/).
|
||||
|
||||
@@ -156,10 +162,13 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
|
||||
an `ast::StmtAssert` node).
|
||||
|
||||
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.rs` (for
|
||||
AST-based checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks),
|
||||
`crates/ruff/src/checkers/lines.rs` (for text-based checks), or
|
||||
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based checks).
|
||||
1. Define the logic for invoking the diagnostic in `crates/ruff/src/checkers/ast/analyze` (for
|
||||
AST-based rules), `crates/ruff/src/checkers/tokens.rs` (for token-based rules),
|
||||
`crates/ruff/src/checkers/physical_lines.rs` (for text-based rules),
|
||||
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based rules), etc. For AST-based rules,
|
||||
you'll likely want to modify `analyze/statement.rs` (if your rule is based on analyzing
|
||||
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
||||
expressions, like function calls).
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `B011`).
|
||||
|
||||
@@ -221,9 +230,12 @@ Once you've completed the code for the rule itself, you can define tests with th
|
||||
For example, if you're adding a new rule named `E402`, you would run:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
|
||||
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
|
||||
```
|
||||
|
||||
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
|
||||
you activate it by adding `--select ${rule_code}` to the command.
|
||||
|
||||
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
|
||||
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
|
||||
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
|
||||
@@ -310,7 +322,7 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||
main has your merged PR as last commit
|
||||
1. The release workflow will do the following:
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven’t tagged or
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or
|
||||
uploaded anything, you can restart after pushing a fix.
|
||||
1. Upload to PyPI.
|
||||
1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only
|
||||
@@ -723,9 +735,9 @@ diagnostics, then our current compilation pipeline proceeds as follows:
|
||||
|
||||
1. **File discovery**: Given paths like `foo/`, locate all Python files in any specified subdirectories, taking into account our hierarchical settings system and any `exclude` options.
|
||||
|
||||
1. **Package resolution**: Determine the “package root” for every file by traversing over its parent directories and looking for `__init__.py` files.
|
||||
1. **Package resolution**: Determine the "package root" for every file by traversing over its parent directories and looking for `__init__.py` files.
|
||||
|
||||
1. **Cache initialization**: For every “package root”, initialize an empty cache.
|
||||
1. **Cache initialization**: For every "package root", initialize an empty cache.
|
||||
|
||||
1. **Analysis**: For every file, in parallel:
|
||||
|
||||
@@ -733,7 +745,7 @@ diagnostics, then our current compilation pipeline proceeds as follows:
|
||||
|
||||
1. **Tokenization**: Run the lexer over the file to generate a token stream.
|
||||
|
||||
1. **Indexing**: Extract metadata from the token stream, such as: comment ranges, `# noqa` locations, `# isort: off` locations, “doc lines”, etc.
|
||||
1. **Indexing**: Extract metadata from the token stream, such as: comment ranges, `# noqa` locations, `# isort: off` locations, "doc lines", etc.
|
||||
|
||||
1. **Token-based rule evaluation**: Run any lint rules that are based on the contents of the token stream (e.g., commented-out code).
|
||||
|
||||
@@ -743,9 +755,9 @@ diagnostics, then our current compilation pipeline proceeds as follows:
|
||||
|
||||
1. **Parsing**: Run the parser over the token stream to produce an AST. (This consumes the token stream, so anything that relies on the token stream needs to happen before parsing.)
|
||||
|
||||
1. **AST-based rule evaluation**: Run any lint rules that are based on the AST. This includes the vast majority of lint rules. As part of this step, we also build the semantic model for the current file as we traverse over the AST. Some lint rules are evaluated eagerly, as we iterate over the AST, while others are evaluated in a deferred manner (e.g., unused imports, since we can’t determine whether an import is unused until we’ve finished analyzing the entire file), after we’ve finished the initial traversal.
|
||||
1. **AST-based rule evaluation**: Run any lint rules that are based on the AST. This includes the vast majority of lint rules. As part of this step, we also build the semantic model for the current file as we traverse over the AST. Some lint rules are evaluated eagerly, as we iterate over the AST, while others are evaluated in a deferred manner (e.g., unused imports, since we can't determine whether an import is unused until we've finished analyzing the entire file), after we've finished the initial traversal.
|
||||
|
||||
1. **Import-based rule evaluation**: Run any lint rules that are based on the module’s imports (e.g., import sorting). These could, in theory, be included in the AST-based rule evaluation phase — they’re just separated for simplicity.
|
||||
1. **Import-based rule evaluation**: Run any lint rules that are based on the module's imports (e.g., import sorting). These could, in theory, be included in the AST-based rule evaluation phase — they're just separated for simplicity.
|
||||
|
||||
1. **Physical line-based rule evaluation**: Run any lint rules that are based on physical lines (e.g., line-length).
|
||||
|
||||
@@ -754,3 +766,116 @@ diagnostics, then our current compilation pipeline proceeds as follows:
|
||||
1. **Cache write**: Write the generated diagnostics to the package cache using the file as a key.
|
||||
|
||||
1. **Reporting**: Print diagnostics in the specified format (text, JSON, etc.), to the specified output channel (stdout, a file, etc.).
|
||||
|
||||
### Import Categorization
|
||||
|
||||
To understand Ruff's import categorization system, we first need to define two concepts:
|
||||
|
||||
- "Project root": The directory containing the `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file,
|
||||
discovered by identifying the "closest" such directory for each Python file. (If you're running
|
||||
via `ruff --config /path/to/pyproject.toml`, then the current working directory is used as the
|
||||
"project root".)
|
||||
- "Package root": The top-most directory defining the Python package that includes a given Python
|
||||
file. To find the package root for a given Python file, traverse up its parent directories until
|
||||
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
|
||||
a [namespace package](https://beta.ruff.rs/docs/settings/#namespace-packages)); take the directory
|
||||
just before that, i.e., the first directory in the package.
|
||||
|
||||
For example, given:
|
||||
|
||||
```text
|
||||
my_project
|
||||
├── pyproject.toml
|
||||
└── src
|
||||
└── foo
|
||||
├── __init__.py
|
||||
└── bar
|
||||
├── __init__.py
|
||||
└── baz.py
|
||||
```
|
||||
|
||||
Then when analyzing `baz.py`, the project root would be the top-level directory (`./my_project`),
|
||||
and the package root would be `./my_project/src/foo`.
|
||||
|
||||
#### Project root
|
||||
|
||||
The project root does not have a significant impact beyond that all relative paths within the loaded
|
||||
configuration file are resolved relative to the project root.
|
||||
|
||||
For example, to indicate that `bar` above is a namespace package (it isn't, but let's run with it),
|
||||
the `pyproject.toml` would list `namespace-packages = ["./src/bar"]`, which would resolve
|
||||
to `my_project/src/bar`.
|
||||
|
||||
The same logic applies when providing a configuration file via `--config`. In that case, the
|
||||
_current working directory_ is used as the project root, and so all paths in that configuration file
|
||||
are resolved relative to the current working directory. (As a general rule, we want to avoid relying
|
||||
on the current working directory as much as possible, to ensure that Ruff exhibits the same behavior
|
||||
regardless of where and how you invoke it — but that's hard to avoid in this case.)
|
||||
|
||||
Additionally, if a `pyproject.toml` file _extends_ another configuration file, Ruff will still use
|
||||
the directory containing that `pyproject.toml` file as the project root. For example, if
|
||||
`./my_project/pyproject.toml` contains:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend = "/path/to/pyproject.toml"
|
||||
```
|
||||
|
||||
Then Ruff will use `./my_project` as the project root, even though the configuration file extends
|
||||
`/path/to/pyproject.toml`. As such, if the configuration file at `/path/to/pyproject.toml` contains
|
||||
any relative paths, they will be resolved relative to `./my_project`.
|
||||
|
||||
If a project uses nested configuration files, then Ruff would detect multiple project roots, one for
|
||||
each configuration file.
|
||||
|
||||
#### Package root
|
||||
|
||||
The package root is used to determine a file's "module path". Consider, again, `baz.py`. In that
|
||||
case, `./my_project/src/foo` was identified as the package root, so the module path for `baz.py`
|
||||
would resolve to `foo.bar.baz` — as computed by taking the relative path from the package root
|
||||
(inclusive of the root itself). The module path can be thought of as "the path you would use to
|
||||
import the module" (e.g., `import foo.bar.baz`).
|
||||
|
||||
The package root and module path are used to, e.g., convert relative to absolute imports, and for
|
||||
import categorization, as described below.
|
||||
|
||||
#### Import categorization
|
||||
|
||||
When sorting and formatting import blocks, Ruff categorizes every import into one of five
|
||||
categories:
|
||||
|
||||
1. **"Future"**: the import is a `__future__` import. That's easy: just look at the name of the
|
||||
imported module!
|
||||
1. **"Standard library"**: the import comes from the Python standard library (e.g., `import os`).
|
||||
This is easy too: we include a list of all known standard library modules in Ruff itself, so it's
|
||||
a simple lookup.
|
||||
1. **"Local folder"**: the import is a relative import (e.g., `from .foo import bar`). This is easy
|
||||
too: just check if the import includes a `level` (i.e., a dot-prefix).
|
||||
1. **"First party"**: the import is part of the current project. (More on this below.)
|
||||
1. **"Third party"**: everything else.
|
||||
|
||||
The real challenge lies in determining whether an import is first-party — everything else is either
|
||||
trivial, or (as in the case of third-party) merely defined as "not first-party".
|
||||
|
||||
There are three ways in which an import can be categorized as "first-party":
|
||||
|
||||
1. **Explicit settings**: the import is marked as such via the `known-first-party` setting. (This
|
||||
should generally be seen as an escape hatch.)
|
||||
1. **Same-package**: the imported module is in the same package as the current file. This gets back
|
||||
to the importance of the "package root" and the file's "module path". Imagine that we're
|
||||
analyzing `baz.py` above. If `baz.py` contains any imports that appear to come from the `foo`
|
||||
package (e.g., `from foo import bar` or `import foo.bar`), they'll be classified as first-party
|
||||
automatically. This check is as simple as comparing the first segment of the current file's
|
||||
module path to the first segment of the import.
|
||||
1. **Source roots**: Ruff supports a `[src](https://beta.ruff.rs/docs/settings/#src)` setting, which
|
||||
sets the directories to scan when identifying first-party imports. The algorithm is
|
||||
straightforward: given an import, like `import foo`, iterate over the directories enumerated in
|
||||
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
|
||||
file `foo.py`.
|
||||
|
||||
By default, `src` is set to the project root. In the above example, we'd want to set
|
||||
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
|
||||
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
|
||||
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
|
||||
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
|
||||
explicitly.
|
||||
|
||||
492
Cargo.lock
generated
492
Cargo.lock
generated
@@ -133,6 +133,21 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
|
||||
dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.11"
|
||||
@@ -169,6 +184,21 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -609,6 +639,16 @@ dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
@@ -632,6 +672,17 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
@@ -656,6 +707,15 @@ version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
@@ -732,9 +792,15 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.280"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -967,6 +1033,7 @@ dependencies = [
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1099,6 +1166,28 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"bit-set",
|
||||
"diff",
|
||||
"ena",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
"regex-syntax 0.7.3",
|
||||
"string_cache",
|
||||
"term",
|
||||
"tiny-keccak",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.20.0"
|
||||
@@ -1199,6 +1288,16 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
@@ -1277,6 +1376,12 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nextest-workspace-hack"
|
||||
version = "0.1.0"
|
||||
@@ -1295,12 +1400,6 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -1427,6 +1526,29 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.13"
|
||||
@@ -1518,14 +1640,23 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 1.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1535,7 +1666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1544,21 +1675,17 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"phf_shared 0.11.2",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.2"
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1621,6 +1748,18 @@ version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.0.3"
|
||||
@@ -1745,6 +1884,18 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
@@ -1753,6 +1904,9 @@ name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
@@ -1888,7 +2042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.280"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1909,14 +2063,12 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"natord",
|
||||
"nohash-hasher",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"phf",
|
||||
"pretty_assertions",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
@@ -1927,15 +2079,16 @@ dependencies = [
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_index",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_rustpython",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_textwrap",
|
||||
"rustc-hash",
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -1966,7 +2119,7 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"rustpython-parser",
|
||||
"ruff_python_parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tikv-jemallocator",
|
||||
@@ -1988,7 +2141,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.280"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2017,11 +2170,13 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_textwrap",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2046,7 +2201,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pretty_assertions",
|
||||
"rayon",
|
||||
@@ -2055,11 +2209,13 @@ dependencies = [
|
||||
"ruff_cli",
|
||||
"ruff_diagnostics",
|
||||
"ruff_formatter",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_textwrap",
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
"ruff_python_trivia",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2068,6 +2224,9 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2110,7 +2269,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_textwrap",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
@@ -2118,24 +2277,32 @@ dependencies = [
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.3.3",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"log",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
"rustpython-literal",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_codegen"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_source_file",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2152,10 +2319,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ruff_formatter",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
@@ -2163,6 +2332,55 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"lexical-parse-float",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"static_assertions",
|
||||
"tiny-keccak",
|
||||
"unic-emoji-char",
|
||||
"unic-ucd-ident",
|
||||
"unicode_names2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_resolver"
|
||||
version = "0.0.0"
|
||||
@@ -2179,14 +2397,13 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"is-macro",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-parser",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -2200,19 +2417,14 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"memchr",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"smallvec",
|
||||
"unic-ucd-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_rustpython"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.0"
|
||||
@@ -2222,28 +2434,32 @@ dependencies = [
|
||||
"fs-err",
|
||||
"regex",
|
||||
"ruff_python_ast",
|
||||
"ruff_rustpython",
|
||||
"rustpython-ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"shlex",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"insta",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"ruff_text_size",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_textwrap"
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_text_size",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_test",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2257,9 +2473,11 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_diagnostics",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_formatter",
|
||||
"ruff_rustpython",
|
||||
"rustpython-parser",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"ruff_source_file",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"wasm-bindgen",
|
||||
@@ -2331,74 +2549,6 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
"rustpython-parser-core",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-format"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"itertools",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rustpython-literal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
"lexical-parse-float",
|
||||
"num-traits",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"lalrpop-util",
|
||||
"log",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
"rustpython-parser-core",
|
||||
"tiny-keccak",
|
||||
"unic-emoji-char",
|
||||
"unic-ucd-ident",
|
||||
"unicode_names2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=4d03b9b5b212fc869e4cfda151414438186a7779#4d03b9b5b212fc869e4cfda151414438186a7779"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"memchr",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.13"
|
||||
@@ -2534,6 +2684,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.0.0"
|
||||
@@ -2616,6 +2775,19 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -2689,6 +2861,17 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"rustversion",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
@@ -2942,6 +3125,18 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-indicatif"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b38ed3722d27705c3bd7ca0ccf29acc3d8e1c717b4cd87f97891a2c1834ea1af"
|
||||
dependencies = [
|
||||
"indicatif",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
@@ -3068,6 +3263,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "0.6.0"
|
||||
@@ -3134,6 +3335,39 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vt100"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"log",
|
||||
"unicode-width",
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -4,7 +4,7 @@ resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
homepage = "https://beta.ruff.rs/docs"
|
||||
documentation = "https://beta.ruff.rs/docs"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -26,7 +26,6 @@ is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
@@ -47,17 +46,14 @@ syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f05707e50703b49fe3dd860aa839", default-features = false }
|
||||
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "4d03b9b5b212fc869e4cfda151414438186a7779" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "4d03b9b5b212fc869e4cfda151414438186a7779" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "4d03b9b5b212fc869e4cfda151414438186a7779", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "4d03b9b5b212fc869e4cfda151414438186a7779", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "4d03b9b5b212fc869e4cfda151414438186a7779" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
@@ -70,7 +66,7 @@ opt-level = 3
|
||||
|
||||
# Reduce complexity of a parser function that would trigger a locals limit in a wasm tool.
|
||||
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
|
||||
[profile.dev.package.rustpython-parser]
|
||||
[profile.dev.package.ruff_python_parser]
|
||||
opt-level = 1
|
||||
|
||||
# Use the `--profile release-debug` flag to show symbols in release mode.
|
||||
|
||||
12
README.md
12
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
# Ruff
|
||||
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.280
|
||||
rev: v0.0.282
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -424,13 +424,13 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
|
||||
|
||||
```md
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
```
|
||||
|
||||
...or `README.rst`:
|
||||
|
||||
```rst
|
||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
|
||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
||||
:target: https://github.com/astral-sh/ruff
|
||||
:alt: Ruff
|
||||
```
|
||||
@@ -438,7 +438,7 @@ If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
|
||||
...or, as HTML:
|
||||
|
||||
```html
|
||||
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
|
||||
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
|
||||
```
|
||||
|
||||
## License
|
||||
@@ -447,6 +447,6 @@ MIT
|
||||
|
||||
<div align="center">
|
||||
<a target="_blank" href="https://astral.sh" style="background:none">
|
||||
<img src="https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/svg/Astral.svg">
|
||||
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.280"
|
||||
version = "0.0.282"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.280"
|
||||
version = "0.0.282"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -19,13 +19,16 @@ ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
ruff_python_index = { path = "../ruff_python_index" }
|
||||
ruff_python_literal = { path = "../ruff_python_literal" }
|
||||
ruff_python_semantic = { path = "../ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_rustpython = { path = "../ruff_rustpython" }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_textwrap = { path = "../ruff_textwrap" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
@@ -45,7 +48,6 @@ libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
@@ -55,14 +57,13 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
pyproject-toml = { version = "0.6.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
rustpython-format = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
|
||||
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -67,7 +67,8 @@ cfg.getboolean("hello", True)
|
||||
os.set_blocking(0, False)
|
||||
g_action.set_enabled(True)
|
||||
settings.set_enable_developer_extras(True)
|
||||
|
||||
foo.is_(True)
|
||||
bar.is_not(False)
|
||||
|
||||
class Registry:
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Should emit:
|
||||
B002 - on lines 15 and 20
|
||||
B002 - on lines 18, 19, and 24
|
||||
"""
|
||||
|
||||
|
||||
@@ -8,13 +8,17 @@ def this_is_all_fine(n):
|
||||
x = n + 1
|
||||
y = 1 + n
|
||||
z = +x + y
|
||||
return +z
|
||||
a = n - 1
|
||||
b = 1 - n
|
||||
c = -a - b
|
||||
return +z, -c
|
||||
|
||||
|
||||
def this_is_buggy(n):
|
||||
x = ++n
|
||||
return x
|
||||
y = --n
|
||||
return x, y
|
||||
|
||||
|
||||
def this_is_buggy_too(n):
|
||||
return ++n
|
||||
return ++n, --n
|
||||
|
||||
@@ -17,3 +17,37 @@ from typing import TypedDict
|
||||
|
||||
class MyClass(TypedDict):
|
||||
id: int
|
||||
|
||||
|
||||
from threading import Event
|
||||
|
||||
|
||||
class CustomEvent(Event):
|
||||
def set(self) -> None:
|
||||
...
|
||||
|
||||
def str(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
from logging import Filter, LogRecord
|
||||
|
||||
|
||||
class CustomFilter(Filter):
|
||||
def filter(self, record: LogRecord) -> bool:
|
||||
...
|
||||
|
||||
def str(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
class MyClass:
|
||||
@override
|
||||
def str(self):
|
||||
pass
|
||||
|
||||
def int(self):
|
||||
pass
|
||||
|
||||
2
crates/ruff/resources/test/fixtures/flake8_executable/EXE004_4.py
vendored
Executable file
2
crates/ruff/resources/test/fixtures/flake8_executable/EXE004_4.py
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
|
||||
#!/usr/bin/env python
|
||||
@@ -50,3 +50,12 @@ _ = """a""" "b"
|
||||
_ = 'a' "b"
|
||||
|
||||
_ = rf"a" rf"b"
|
||||
|
||||
# Single-line explicit concatenation should be ignored.
|
||||
_ = "abc" + "def" + "ghi"
|
||||
_ = foo + "abc" + "def"
|
||||
_ = "abc" + foo + "def"
|
||||
_ = "abc" + "def" + foo
|
||||
_ = foo + bar + "abc"
|
||||
_ = "abc" + foo + bar
|
||||
_ = foo + "abc" + bar
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import logging
|
||||
from distutils import log
|
||||
|
||||
from logging_setup import logger
|
||||
|
||||
logging.warn("Hello World!")
|
||||
log.warn("Hello world!") # This shouldn't be considered as a logger candidate
|
||||
logger.warn("Hello world!")
|
||||
|
||||
logging . warn("Hello World!")
|
||||
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import typing
|
||||
from typing import TypeVar
|
||||
|
||||
_T = typing.TypeVar("_T")
|
||||
_P = TypeVar("_P")
|
||||
|
||||
# OK
|
||||
_UsedTypeVar = TypeVar("_UsedTypeVar")
|
||||
def func(arg: _UsedTypeVar) -> _UsedTypeVar: ...
|
||||
|
||||
_A, _B = TypeVar("_A"), TypeVar("_B")
|
||||
_C = _D = TypeVar("_C")
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import typing
|
||||
from typing import TypeVar
|
||||
|
||||
_T = typing.TypeVar("_T")
|
||||
_P = TypeVar("_P")
|
||||
|
||||
# OK
|
||||
_UsedTypeVar = TypeVar("_UsedTypeVar")
|
||||
def func(arg: _UsedTypeVar) -> _UsedTypeVar: ...
|
||||
|
||||
_A, _B = TypeVar("_A"), TypeVar("_B")
|
||||
_C = _D = TypeVar("_C")
|
||||
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.py
vendored
Normal file
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import TypeVar, Self, Type
|
||||
|
||||
_S = TypeVar("_S", bound=BadClass)
|
||||
_S2 = TypeVar("_S2", BadClass, GoodClass)
|
||||
|
||||
class BadClass:
|
||||
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
class GoodClass:
|
||||
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
|
||||
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
|
||||
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
|
||||
@classmethod
|
||||
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
|
||||
@classmethod
|
||||
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
|
||||
@staticmethod
|
||||
def static_method(arg1: _S) -> _S: ...
|
||||
|
||||
|
||||
# Python > 3.12
|
||||
class PEP695BadDunderNew[T]:
|
||||
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
|
||||
|
||||
def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.pyi
vendored
Normal file
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.pyi
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import TypeVar, Self, Type
|
||||
|
||||
_S = TypeVar("_S", bound=BadClass)
|
||||
_S2 = TypeVar("_S2", BadClass, GoodClass)
|
||||
|
||||
class BadClass:
|
||||
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
class GoodClass:
|
||||
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
|
||||
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
|
||||
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
|
||||
@classmethod
|
||||
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
|
||||
@classmethod
|
||||
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
|
||||
@staticmethod
|
||||
def static_method(arg1: _S) -> _S: ...
|
||||
|
||||
|
||||
# Python > 3.12
|
||||
class PEP695BadDunderNew[T]:
|
||||
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
|
||||
|
||||
def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
@@ -1,9 +1,11 @@
|
||||
import collections
|
||||
|
||||
person: collections.namedtuple # OK
|
||||
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
person: namedtuple # OK
|
||||
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
person = namedtuple("Person", ["name", "age"]) # OK
|
||||
person = namedtuple(
|
||||
"Person", ["name", "age"]
|
||||
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
import typing
|
||||
import typing_extensions
|
||||
from typing import Literal
|
||||
# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi.
|
||||
# Note that this rule could be applied here in the future.
|
||||
|
||||
# Shouldn't affect non-union field types.
|
||||
field1: Literal[1] # OK
|
||||
field2: Literal[1] | Literal[2] # OK
|
||||
|
||||
def func1(arg1: Literal[1] | Literal[2]): # OK
|
||||
# Should emit for duplicate field types.
|
||||
field2: Literal[1] | Literal[2] # Error
|
||||
|
||||
# Should emit for union types in arguments.
|
||||
def func1(arg1: Literal[1] | Literal[2]): # Error
|
||||
print(arg1)
|
||||
|
||||
|
||||
def func2() -> Literal[1] | Literal[2]: # OK
|
||||
# Should emit for unions in return types.
|
||||
def func2() -> Literal[1] | Literal[2]: # Error
|
||||
return "my Literal[1]ing"
|
||||
|
||||
|
||||
field3: Literal[1] | Literal[2] | str # OK
|
||||
field4: str | Literal[1] | Literal[2] # OK
|
||||
field5: Literal[1] | str | Literal[2] # OK
|
||||
field6: Literal[1] | bool | Literal[2] | str # OK
|
||||
field7 = Literal[1] | Literal[2] # OK
|
||||
field8: Literal[1] | (Literal[2] | str) # OK
|
||||
field9: Literal[1] | (Literal[2] | str) # OK
|
||||
field10: (Literal[1] | str) | Literal[2] # OK
|
||||
field11: dict[Literal[1] | Literal[2], str] # OK
|
||||
# Should emit in longer unions, even if not directly adjacent.
|
||||
field3: Literal[1] | Literal[2] | str # Error
|
||||
field4: str | Literal[1] | Literal[2] # Error
|
||||
field5: Literal[1] | str | Literal[2] # Error
|
||||
field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
||||
# Should emit for non-type unions.
|
||||
field7 = Literal[1] | Literal[2] # Error
|
||||
|
||||
# Should emit for parenthesized unions.
|
||||
field8: Literal[1] | (Literal[2] | str) # Error
|
||||
|
||||
# Should handle user parentheses when fixing.
|
||||
field9: Literal[1] | (Literal[2] | str) # Error
|
||||
field10: (Literal[1] | str) | Literal[2] # Error
|
||||
|
||||
# Should emit for union in generic parent type.
|
||||
field11: dict[Literal[1] | Literal[2], str] # Error
|
||||
|
||||
@@ -3,8 +3,8 @@ import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Fine because not a stub file
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Fine because not a stub file
|
||||
def __eq__(self, other: Any) -> bool: ... # Y032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
|
||||
|
||||
|
||||
class Good:
|
||||
|
||||
@@ -9,16 +9,16 @@ from typing import (
|
||||
|
||||
just_literals_pipe_union: TypeAlias = (
|
||||
Literal[True] | Literal["idk"]
|
||||
) # not PYI042 (not a stubfile)
|
||||
) # PYI042, since not camel case
|
||||
PublicAliasT: TypeAlias = str | int
|
||||
PublicAliasT2: TypeAlias = Union[str, bytes]
|
||||
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
|
||||
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
|
||||
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
snake_case_alias1: TypeAlias = str | int # not PYI042 (not a stubfile)
|
||||
_snake_case_alias2: TypeAlias = Literal["whatever"] # not PYI042 (not a stubfile)
|
||||
Snake_case_alias: TypeAlias = int | float # not PYI042 (not a stubfile)
|
||||
snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
|
||||
_snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case
|
||||
Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
@@ -7,11 +7,11 @@ from typing import (
|
||||
Literal,
|
||||
)
|
||||
|
||||
_PrivateAliasT: TypeAlias = str | int # not PYI043 (not a stubfile)
|
||||
_PrivateAliasT2: TypeAlias = typing.Any # not PYI043 (not a stubfile)
|
||||
_PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
|
||||
_PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
|
||||
_PrivateAliasT3: TypeAlias = Literal[
|
||||
"not", "a", "chance"
|
||||
] # not PYI043 (not a stubfile)
|
||||
] # PYI043, since this ends in a T
|
||||
just_literals_pipe_union: TypeAlias = Literal[True] | Literal["idk"]
|
||||
PublicAliasT: TypeAlias = str | int
|
||||
PublicAliasT2: TypeAlias = Union[str, bytes]
|
||||
|
||||
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI046.py
vendored
Normal file
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI046.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import typing
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class _Foo(Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
class _Bar(typing.Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
# OK
|
||||
class _UsedPrivateProtocol(Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
def uses__UsedPrivateProtocol(arg: _UsedPrivateProtocol) -> None: ...
|
||||
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI046.pyi
vendored
Normal file
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI046.pyi
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import typing
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class _Foo(object, Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
class _Bar(typing.Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
# OK
|
||||
class _UsedPrivateProtocol(Protocol):
|
||||
bar: int
|
||||
|
||||
|
||||
def uses__UsedPrivateProtocol(arg: _UsedPrivateProtocol) -> None: ...
|
||||
22
crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.py
vendored
Normal file
22
crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import typing
|
||||
import sys
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
_UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
_T: typing.TypeAlias = str
|
||||
|
||||
# OK
|
||||
_UsedPrivateTypeAlias: TypeAlias = int | None
|
||||
|
||||
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
|
||||
...
|
||||
|
||||
|
||||
if sys.version_info > (3, 9):
|
||||
_PrivateTypeAlias: TypeAlias = str | None
|
||||
else:
|
||||
_PrivateTypeAlias: TypeAlias = float | None
|
||||
|
||||
|
||||
def func2(arg: _PrivateTypeAlias) -> None: ...
|
||||
22
crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.pyi
vendored
Normal file
22
crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.pyi
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import typing
|
||||
import sys
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
_UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
_T: typing.TypeAlias = str
|
||||
|
||||
# OK
|
||||
_UsedPrivateTypeAlias: TypeAlias = int | None
|
||||
|
||||
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
|
||||
...
|
||||
|
||||
|
||||
if sys.version_info > (3, 9):
|
||||
_PrivateTypeAlias: TypeAlias = str | None
|
||||
else:
|
||||
_PrivateTypeAlias: TypeAlias = float | None
|
||||
|
||||
|
||||
def func2(arg: _PrivateTypeAlias) -> None: ...
|
||||
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI049.py
vendored
Normal file
18
crates/ruff/resources/test/fixtures/flake8_pyi/PYI049.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import typing
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class _UnusedTypedDict(TypedDict):
|
||||
foo: str
|
||||
|
||||
|
||||
class _UnusedTypedDict2(typing.TypedDict):
|
||||
bar: int
|
||||
|
||||
|
||||
class _UsedTypedDict(TypedDict):
|
||||
foo: bytes
|
||||
|
||||
|
||||
class _CustomClass(_UsedTypedDict):
|
||||
bar: list[int]
|
||||
32
crates/ruff/resources/test/fixtures/flake8_pyi/PYI049.pyi
vendored
Normal file
32
crates/ruff/resources/test/fixtures/flake8_pyi/PYI049.pyi
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
import typing
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class _UnusedTypedDict(TypedDict):
|
||||
foo: str
|
||||
|
||||
|
||||
class _UnusedTypedDict2(typing.TypedDict):
|
||||
bar: int
|
||||
|
||||
|
||||
# OK
|
||||
class _UsedTypedDict(TypedDict):
|
||||
foo: bytes
|
||||
|
||||
|
||||
class _CustomClass(_UsedTypedDict):
|
||||
bar: list[int]
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
class _UsedTypedDict2(TypedDict):
|
||||
foo: int
|
||||
else:
|
||||
class _UsedTypedDict2(TypedDict):
|
||||
foo: float
|
||||
|
||||
|
||||
class _CustomClass2(_UsedTypedDict2):
|
||||
bar: list[int]
|
||||
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.py
vendored
Normal file
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import typing
|
||||
from typing import Literal, TypeAlias, Union
|
||||
|
||||
A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
# OK
|
||||
A: Literal["foo"]
|
||||
B: TypeAlias = Literal[b"bar", b"foo"]
|
||||
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
|
||||
D: TypeAlias = Literal[b"str_bytes", 42]
|
||||
|
||||
def func(x: Literal[1J], y: Literal[3.14]): ...
|
||||
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.pyi
vendored
Normal file
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.pyi
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import typing
|
||||
from typing import Literal, TypeAlias, Union
|
||||
|
||||
A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
# OK
|
||||
A: Literal["foo"]
|
||||
B: TypeAlias = Literal[b"bar", b"foo"]
|
||||
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
|
||||
D: TypeAlias = Literal[b"str_bytes", 42]
|
||||
|
||||
def func(x: Literal[1J], y: Literal[3.14]): ...
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.py
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
# OK
|
||||
x: type[int, str, float]
|
||||
y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.pyi
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.pyi
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
# OK
|
||||
x: type[int, str, float]
|
||||
y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
__all__ = ["A", "B", "C"]
|
||||
|
||||
# Errors
|
||||
__all__.append("D")
|
||||
__all__.extend(["E", "Foo"])
|
||||
__all__.remove("A")
|
||||
|
||||
# OK
|
||||
__all__ += ["D"]
|
||||
foo = ["Hello"]
|
||||
foo.append("World")
|
||||
foo.bar.append("World")
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
__all__ = ["A", "B", "C"]
|
||||
|
||||
# Errors
|
||||
__all__.append("D")
|
||||
__all__.extend(["E", "Foo"])
|
||||
__all__.remove("A")
|
||||
|
||||
# OK
|
||||
__all__ += ["D"]
|
||||
foo = ["Hello"]
|
||||
foo.append("World")
|
||||
foo.bar.append("World")
|
||||
@@ -11,6 +11,10 @@ async def test_ok_trivial_with():
|
||||
with context_manager_under_test():
|
||||
pass
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
with context_manager_under_test():
|
||||
raise ValueError
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
async with context_manager_under_test():
|
||||
pass
|
||||
@@ -24,6 +28,16 @@ def test_ok_complex_single_call():
|
||||
)
|
||||
|
||||
|
||||
def test_ok_func_and_class():
|
||||
with pytest.raises(AttributeError):
|
||||
class A:
|
||||
pass
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
def test_error_multiple_statements():
|
||||
with pytest.raises(AttributeError):
|
||||
len([])
|
||||
@@ -47,13 +61,10 @@ async def test_error_complex_statement():
|
||||
while True:
|
||||
[].size
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
with context_manager_under_test():
|
||||
[].size
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
async with context_manager_under_test():
|
||||
[].size
|
||||
if True:
|
||||
raise Exception
|
||||
|
||||
|
||||
def test_error_try():
|
||||
|
||||
@@ -156,3 +156,12 @@ if False:
|
||||
if True:
|
||||
if a:
|
||||
pass
|
||||
|
||||
|
||||
# SIM102
|
||||
def f():
|
||||
if a:
|
||||
pass
|
||||
elif b:
|
||||
if c:
|
||||
d
|
||||
|
||||
@@ -14,6 +14,12 @@ try:
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except (ValueError, OSError) as e:
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
@@ -94,3 +100,13 @@ def with_comment():
|
||||
foo()
|
||||
except (ValueError, OSError):
|
||||
pass # Trailing comment.
|
||||
|
||||
try:
|
||||
print()
|
||||
except ("not", "an", "exception"):
|
||||
pass
|
||||
|
||||
try:
|
||||
print()
|
||||
except "not an exception":
|
||||
pass
|
||||
|
||||
@@ -101,3 +101,16 @@ if a:
|
||||
x = 1
|
||||
elif c:
|
||||
x = 1
|
||||
|
||||
|
||||
def foo():
|
||||
a = True
|
||||
b = False
|
||||
if a > b: # end-of-line
|
||||
return 3
|
||||
elif a == b:
|
||||
return 3
|
||||
elif a < b: # end-of-line
|
||||
return 4
|
||||
elif b is None:
|
||||
return 4
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import contextlib
|
||||
import pathlib
|
||||
import pathlib as pl
|
||||
from pathlib import Path
|
||||
from pathlib import Path as P
|
||||
|
||||
# SIM115
|
||||
f = open("foo.txt")
|
||||
f = Path("foo.txt").open()
|
||||
f = pathlib.Path("foo.txt").open()
|
||||
f = pl.Path("foo.txt").open()
|
||||
f = P("foo.txt").open()
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
key in obj.keys() # SIM118
|
||||
|
||||
key not in obj.keys() # SIM118
|
||||
|
||||
foo["bar"] in obj.keys() # SIM118
|
||||
|
||||
foo["bar"] not in obj.keys() # SIM118
|
||||
|
||||
foo['bar'] in obj.keys() # SIM118
|
||||
|
||||
foo['bar'] not in obj.keys() # SIM118
|
||||
|
||||
foo() in obj.keys() # SIM118
|
||||
|
||||
foo() not in obj.keys() # SIM118
|
||||
|
||||
for key in obj.keys(): # SIM118
|
||||
pass
|
||||
|
||||
@@ -22,3 +30,13 @@ for key in list(obj.keys()):
|
||||
(k for k in obj.keys()) # SIM118
|
||||
|
||||
key in (obj or {}).keys() # SIM118
|
||||
|
||||
from typing import KeysView
|
||||
|
||||
|
||||
class Foo:
|
||||
def keys(self) -> KeysView[object]:
|
||||
...
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self.keys() # OK
|
||||
|
||||
10
crates/ruff/resources/test/fixtures/flake8_type_checking/snapshot.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_type_checking/snapshot.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Regression test: ensure that we don't treat the export entry as a typing-only reference."""
|
||||
from __future__ import annotations
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
__all__ = ("getLogger",)
|
||||
|
||||
|
||||
def foo() -> None:
|
||||
pass
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path as pth
|
||||
_ = Path(".")
|
||||
_ = pth(".")
|
||||
_ = PurePath(".")
|
||||
_ = Path("")
|
||||
|
||||
# no match
|
||||
_ = Path()
|
||||
|
||||
22
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH206.py
vendored
Normal file
22
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH206.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
from os import sep
|
||||
|
||||
|
||||
file_name = "foo/bar"
|
||||
|
||||
# PTH206
|
||||
"foo/bar/".split(os.sep)
|
||||
"foo/bar/".split(sep=os.sep)
|
||||
"foo/bar/".split(os.sep)[-1]
|
||||
"foo/bar/".split(os.sep)[-2]
|
||||
"foo/bar/".split(os.sep)[-2:]
|
||||
"fizz/buzz".split(sep)
|
||||
"fizz/buzz".split(sep)[-1]
|
||||
os.path.splitext("path/to/hello_world.py")[0].split(os.sep)[-1]
|
||||
file_name.split(os.sep)
|
||||
(os.path.abspath(file_name)).split(os.sep)
|
||||
|
||||
# OK
|
||||
"foo/bar/".split("/")
|
||||
"foo/bar/".split(os.sep, 1)
|
||||
"foo/bar/".split(1, sep=os.sep)
|
||||
11
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH207.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_use_pathlib/PTH207.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
import glob
|
||||
from glob import glob as search
|
||||
|
||||
|
||||
extensions_dir = "./extensions"
|
||||
|
||||
# PTH207
|
||||
glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
|
||||
list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
|
||||
search("*.png")
|
||||
2
crates/ruff/resources/test/fixtures/isort/bom_sorted.py
vendored
Normal file
2
crates/ruff/resources/test/fixtures/isort/bom_sorted.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import bar
|
||||
import foo
|
||||
2
crates/ruff/resources/test/fixtures/isort/bom_unsorted.py
vendored
Normal file
2
crates/ruff/resources/test/fixtures/isort/bom_unsorted.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import foo
|
||||
import bar
|
||||
@@ -1 +1 @@
|
||||
broken "§=($/=(")
|
||||
broken "§=($/=()
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
# PERF203
|
||||
for i in range(10):
|
||||
try: # PERF203
|
||||
try:
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK
|
||||
try:
|
||||
for i in range(10):
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK
|
||||
i = 0
|
||||
while i < 10: # PERF203
|
||||
while i < 10:
|
||||
try:
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
i += 1
|
||||
|
||||
try:
|
||||
i = 0
|
||||
while i < 10:
|
||||
print(f"{i}")
|
||||
i += 1
|
||||
except:
|
||||
print("error")
|
||||
|
||||
@@ -45,3 +45,18 @@ def f():
|
||||
for i in items:
|
||||
if i not in result:
|
||||
result.append(i) # OK
|
||||
|
||||
|
||||
def f():
|
||||
fibonacci = [0, 1]
|
||||
for i in range(20):
|
||||
fibonacci.append(sum(fibonacci[-2:])) # OK
|
||||
print(fibonacci)
|
||||
|
||||
|
||||
def f():
|
||||
foo = object()
|
||||
foo.fibonacci = [0, 1]
|
||||
for i in range(20):
|
||||
foo.fibonacci.append(sum(foo.fibonacci[-2:])) # OK
|
||||
print(foo.fibonacci)
|
||||
|
||||
13
crates/ruff/resources/test/fixtures/pycodestyle/E24.py
vendored
Normal file
13
crates/ruff/resources/test/fixtures/pycodestyle/E24.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#: E241
|
||||
a = (1, 2)
|
||||
#: Okay
|
||||
b = (1, 20)
|
||||
#: E242
|
||||
a = (1, 2) # tab before 2
|
||||
#: Okay
|
||||
b = (1, 20) # space before 20
|
||||
#: E241 E241 E241
|
||||
# issue 135
|
||||
more_spaces = [a, b,
|
||||
ef, +h,
|
||||
c, -d]
|
||||
@@ -66,3 +66,6 @@ while 1:
|
||||
#: E703:2:1
|
||||
0\
|
||||
;
|
||||
#: E701:2:3
|
||||
a = \
|
||||
5;
|
||||
|
||||
@@ -58,3 +58,6 @@ assert type(res) == type(None)
|
||||
types = StrEnum
|
||||
if x == types.X:
|
||||
pass
|
||||
|
||||
#: E721
|
||||
assert type(res) is int
|
||||
|
||||
@@ -41,3 +41,5 @@ regex = '\w' # noqa
|
||||
regex = '''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = '\\\_'
|
||||
|
||||
@@ -23,3 +23,5 @@ a = []
|
||||
'%s %s' % (*a,)
|
||||
k = {}
|
||||
'%(k)s' % {**k}
|
||||
'%s' % [1, 2, 3]
|
||||
'%s' % {1, 2, 3}
|
||||
|
||||
11
crates/ruff/resources/test/fixtures/pyflakes/F821_16.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/pyflakes/F821_16.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Test case: `Literal` with `__future__` annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal, Final
|
||||
|
||||
from typing_extensions import assert_type
|
||||
|
||||
CONSTANT: Final = "ns"
|
||||
|
||||
assert_type(CONSTANT, Literal["ns"])
|
||||
103
crates/ruff/resources/test/fixtures/pyflakes/F821_17.py
vendored
Normal file
103
crates/ruff/resources/test/fixtures/pyflakes/F821_17.py
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Test type parameters and aliases"""
|
||||
|
||||
# Type parameters in type alias statements
|
||||
|
||||
from some_module import Bar
|
||||
|
||||
type Foo[T] = T # OK
|
||||
type Foo[T] = list[T] # OK
|
||||
type Foo[T: ForwardA] = T # OK
|
||||
type Foo[*Ts] = Bar[Ts] # OK
|
||||
type Foo[**P] = Bar[P] # OK
|
||||
class ForwardA: ...
|
||||
|
||||
# Types used in aliased assignment must exist
|
||||
|
||||
type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
|
||||
type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
|
||||
|
||||
# Type parameters do not escape alias scopes
|
||||
|
||||
type Foo[T] = T
|
||||
T # F821: Undefined name `T` - not accessible afterward alias scope
|
||||
|
||||
# Type parameters in functions
|
||||
|
||||
def foo[T](t: T) -> T: return t # OK
|
||||
async def afoo[T](t: T) -> T: return t # OK
|
||||
def with_forward_ref[T: ForwardB](t: T) -> T: return t # OK
|
||||
def can_access_inside[T](t: T) -> T: # OK
|
||||
print(T) # OK
|
||||
return t # OK
|
||||
class ForwardB: ...
|
||||
|
||||
|
||||
# Type parameters do not escape function scopes
|
||||
|
||||
from some_library import some_decorator
|
||||
|
||||
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
|
||||
def foo[T](t: T) -> None: ...
|
||||
T # F821: Undefined name `T` - not accessible afterward function scope
|
||||
|
||||
|
||||
# Type parameters in classes
|
||||
|
||||
class Foo[T](list[T]): ... # OK
|
||||
class UsesForward[T: ForwardC](list[T]): ... # OK
|
||||
class ForwardC: ...
|
||||
class WithinBody[T](list[T]): # OK
|
||||
t = T # OK
|
||||
x: T # OK
|
||||
|
||||
def foo(self, x: T) -> T: # OK
|
||||
return x
|
||||
|
||||
def foo(self):
|
||||
T # OK
|
||||
|
||||
|
||||
# Type parameters do not escape class scopes
|
||||
|
||||
from some_library import some_decorator
|
||||
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
|
||||
class Foo[T](list[T]): ...
|
||||
T # F821: Undefined name `T` - not accessible after class scope
|
||||
|
||||
# Types specified in bounds should exist
|
||||
|
||||
type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
|
||||
def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
|
||||
class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
|
||||
type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
||||
# Type parameters in nested classes
|
||||
|
||||
class Parent[T]:
|
||||
t = T # OK
|
||||
|
||||
def can_use_class_variable(self, x: t) -> t: # OK
|
||||
return x
|
||||
|
||||
class Child:
|
||||
def can_access_parent_type_parameter(self, x: T) -> T: # OK
|
||||
T # OK
|
||||
return x
|
||||
|
||||
def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
|
||||
t # F821: Undefined name `t`
|
||||
return x
|
||||
|
||||
# Type parameters in nested functions
|
||||
|
||||
def can_access_inside_nested[T](t: T) -> T: # OK
|
||||
def bar(x: T) -> T: # OK
|
||||
T # OK
|
||||
return x
|
||||
|
||||
bar(t)
|
||||
@@ -39,3 +39,34 @@ class Class:
|
||||
def f(self):
|
||||
print(my_var)
|
||||
my_var = 1
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
print(sys.argv)
|
||||
|
||||
try:
|
||||
3 / 0
|
||||
except ZeroDivisionError:
|
||||
import sys
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
print(sys.argv)
|
||||
|
||||
for sys in range(5):
|
||||
pass
|
||||
|
||||
|
||||
import requests_mock as rm
|
||||
|
||||
|
||||
def requests_mock(requests_mock: rm.Mocker):
|
||||
print(rm.ANY)
|
||||
|
||||
@@ -147,3 +147,10 @@ def f() -> None:
|
||||
global CONSTANT
|
||||
CONSTANT = 1
|
||||
CONSTANT = 2
|
||||
|
||||
|
||||
def f() -> None:
|
||||
try:
|
||||
print("hello")
|
||||
except A as e :
|
||||
print("oh no!")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
x = 1 # type: ignore
|
||||
x = 1 # type:ignore
|
||||
x = 1 # type: ignore[attr-defined] # type: ignore
|
||||
x = 1 # type: ignoreme # type: ignore
|
||||
|
||||
x = 1
|
||||
x = 1 # type ignore
|
||||
|
||||
29
crates/ruff/resources/test/fixtures/pylint/bad_string_format_character.py
vendored
Normal file
29
crates/ruff/resources/test/fixtures/pylint/bad_string_format_character.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# pylint: disable=missing-docstring,consider-using-f-string, pointless-statement
|
||||
|
||||
## Old style formatting
|
||||
|
||||
"%s %z" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"%s" "%z" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"""%s %z""" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"""%s""" """%z""" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
## New style formatting
|
||||
|
||||
"{:s} {:y}".format("hello", "world") # [bad-format-character]
|
||||
|
||||
"{:*^30s}".format("centered")
|
||||
|
||||
## f-strings
|
||||
|
||||
H, W = "hello", "world"
|
||||
f"{H} {W}"
|
||||
f"{H:s} {W:z}" # [bad-format-character]
|
||||
|
||||
f"{1:z}" # [bad-format-character]
|
||||
|
||||
## False negatives
|
||||
|
||||
print(("%" "z") % 1)
|
||||
16
crates/ruff/resources/test/fixtures/pylint/eq_without_hash.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/pylint/eq_without_hash.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
class Person:
|
||||
def __init__(self):
|
||||
self.name = "monty"
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Person) and other.name == self.name
|
||||
|
||||
class Language:
|
||||
def __init__(self):
|
||||
self.name = "python"
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Language) and other.name == self.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
@@ -40,3 +40,4 @@ __all__ = __all__ + ["Hello"]
|
||||
|
||||
__all__ = __all__ + multiprocessing.__all__
|
||||
|
||||
__all__ = list[str](["Hello", "world"])
|
||||
|
||||
41
crates/ruff/resources/test/fixtures/pylint/self_assigning_variable.py
vendored
Normal file
41
crates/ruff/resources/test/fixtures/pylint/self_assigning_variable.py
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
foo = 1
|
||||
bar = 2
|
||||
baz = 3
|
||||
|
||||
# Errors.
|
||||
foo = foo
|
||||
bar = bar
|
||||
foo, bar = foo, bar
|
||||
bar, foo = bar, foo
|
||||
(foo, bar) = (foo, bar)
|
||||
(bar, foo) = (bar, foo)
|
||||
foo, (bar, baz) = foo, (bar, baz)
|
||||
bar, (foo, baz) = bar, (foo, baz)
|
||||
(foo, bar), baz = (foo, bar), baz
|
||||
(foo, (bar, baz)) = (foo, (bar, baz))
|
||||
foo, bar = foo, 1
|
||||
bar, foo = bar, 1
|
||||
(foo, bar) = (foo, 1)
|
||||
(bar, foo) = (bar, 1)
|
||||
foo, (bar, baz) = foo, (bar, 1)
|
||||
bar, (foo, baz) = bar, (foo, 1)
|
||||
(foo, bar), baz = (foo, bar), 1
|
||||
(foo, (bar, baz)) = (foo, (bar, 1))
|
||||
foo: int = foo
|
||||
bar: int = bar
|
||||
|
||||
# Non-errors.
|
||||
foo = bar
|
||||
bar = foo
|
||||
foo, bar = bar, foo
|
||||
foo, bar = bar, foo
|
||||
(foo, bar) = (bar, foo)
|
||||
foo, bar = bar, 1
|
||||
bar, foo = foo, 1
|
||||
foo: int = bar
|
||||
bar: int = 1
|
||||
|
||||
|
||||
class Foo:
|
||||
foo = foo
|
||||
bar = bar
|
||||
18
crates/ruff/resources/test/fixtures/pylint/subprocess_popen_preexec_fn.py
vendored
Normal file
18
crates/ruff/resources/test/fixtures/pylint/subprocess_popen_preexec_fn.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import subprocess
|
||||
|
||||
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
# Errors.
|
||||
subprocess.Popen(preexec_fn=foo)
|
||||
subprocess.Popen(["ls"], preexec_fn=foo)
|
||||
subprocess.Popen(preexec_fn=lambda: print("Hello, world!"))
|
||||
subprocess.Popen(["ls"], preexec_fn=lambda: print("Hello, world!"))
|
||||
|
||||
# Non-errors.
|
||||
subprocess.Popen()
|
||||
subprocess.Popen(["ls"])
|
||||
subprocess.Popen(preexec_fn=None) # None is the default.
|
||||
subprocess.Popen(["ls"], preexec_fn=None) # None is the default.
|
||||
4
crates/ruff/resources/test/fixtures/pyupgrade/UP009_5.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/pyupgrade/UP009_5.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
print('Hello world')
|
||||
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -15,7 +15,22 @@ bytes("foo", **a)
|
||||
bytes(b"foo"
|
||||
b"bar")
|
||||
bytes("foo")
|
||||
bytes(1)
|
||||
f"{f'{str()}'}"
|
||||
int(1.0)
|
||||
int("1")
|
||||
int(b"11")
|
||||
int(10, base=2)
|
||||
int("10", base=2)
|
||||
int("10", 2)
|
||||
float("1.0")
|
||||
float(b"1.0")
|
||||
bool(1)
|
||||
bool(0)
|
||||
bool("foo")
|
||||
bool("")
|
||||
bool(b"")
|
||||
bool(1.0)
|
||||
|
||||
# These become string or byte literals
|
||||
str()
|
||||
@@ -27,3 +42,10 @@ bytes(b"foo")
|
||||
bytes(b"""
|
||||
foo""")
|
||||
f"{str()}"
|
||||
int()
|
||||
int(1)
|
||||
float()
|
||||
float(1.0)
|
||||
bool()
|
||||
bool(True)
|
||||
bool(False)
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
|
||||
"{1} {0}".format(a, b)
|
||||
|
||||
"{0} {1} {0}".format(a, b)
|
||||
|
||||
"{x.y}".format(x=z)
|
||||
|
||||
"{x} {y} {x}".format(x=a, y=b)
|
||||
|
||||
"{.x} {.y}".format(a, b)
|
||||
|
||||
"{} {}".format(a.b, c.d)
|
||||
@@ -72,6 +76,58 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
111111
|
||||
)
|
||||
|
||||
"{a}" "{b}".format(a=1, b=1)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
"{b}"
|
||||
).format(a=1, b=1)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
""
|
||||
"{b}"
|
||||
""
|
||||
).format(a=1, b=1)
|
||||
|
||||
(
|
||||
(
|
||||
# comment
|
||||
"{a}"
|
||||
# comment
|
||||
"{b}"
|
||||
)
|
||||
# comment
|
||||
.format(a=1, b=1)
|
||||
)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
"b"
|
||||
).format(a=1)
|
||||
|
||||
|
||||
def d(osname, version, release):
|
||||
return"{}-{}.{}".format(osname, version, release)
|
||||
|
||||
|
||||
def e():
|
||||
yield"{}".format(1)
|
||||
|
||||
|
||||
assert"{}".format(1)
|
||||
|
||||
|
||||
async def c():
|
||||
return "{}".format(await 3)
|
||||
|
||||
|
||||
async def c():
|
||||
return "{}".format(1 + await 3)
|
||||
|
||||
|
||||
"{}".format(1 * 2)
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
@@ -85,8 +141,6 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
"{} {}".format(*a)
|
||||
|
||||
"{0} {0}".format(arg)
|
||||
|
||||
"{x} {x}".format(arg)
|
||||
|
||||
"{x.y} {x.z}".format(arg)
|
||||
@@ -103,8 +157,6 @@ b"{} {}".format(a, b)
|
||||
|
||||
r'"\N{snowman} {}".format(a)'
|
||||
|
||||
"{a}" "{b}".format(a=1, b=1)
|
||||
|
||||
"123456789 {}".format(
|
||||
11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
)
|
||||
@@ -124,20 +176,25 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
111111
|
||||
)
|
||||
|
||||
async def c():
|
||||
return "{}".format(await 3)
|
||||
"{}".format(
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
)
|
||||
|
||||
"{a}".format(
|
||||
a=[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
)
|
||||
|
||||
async def c():
|
||||
return "{}".format(1 + await 3)
|
||||
(
|
||||
"{a}"
|
||||
"{1 + 2}"
|
||||
).format(a=1)
|
||||
|
||||
|
||||
def d(osname, version, release):
|
||||
return"{}-{}.{}".format(osname, version, release)
|
||||
|
||||
|
||||
def e():
|
||||
yield"{}".format(1)
|
||||
|
||||
|
||||
assert"{}".format(1)
|
||||
"{}".format(**c)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""A mirror of UP037_1.py, with `from __future__ import annotations`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
@@ -1,108 +0,0 @@
|
||||
"""A mirror of UP037_0.py, without `from __future__ import annotations`."""
|
||||
|
||||
from typing import (
|
||||
Annotated,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
TypedDict,
|
||||
cast,
|
||||
)
|
||||
|
||||
from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
|
||||
|
||||
|
||||
def foo(var: "MyClass") -> "MyClass":
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
def foo(*, inplace: "bool"):
|
||||
pass
|
||||
|
||||
|
||||
def foo(*args: "str", **kwargs: "int"):
|
||||
pass
|
||||
|
||||
|
||||
x: Tuple["MyClass"]
|
||||
|
||||
x: Callable[["MyClass"], None]
|
||||
|
||||
|
||||
class Foo(NamedTuple):
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", foo="int", total=False)
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", {"foo": "int"})
|
||||
|
||||
|
||||
x: Annotated["str", "metadata"]
|
||||
|
||||
x: Arg("str", "name")
|
||||
|
||||
x: DefaultArg("str", "name")
|
||||
|
||||
x: NamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", name="name")
|
||||
|
||||
x: VarArg("str")
|
||||
|
||||
x: List[List[List["MyClass"]]]
|
||||
|
||||
x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
||||
X: MyCallable("X")
|
||||
|
||||
|
||||
# OK
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E")
|
||||
|
||||
|
||||
x: Annotated[()]
|
||||
|
||||
x: DefaultNamedArg(name="name", quox="str")
|
||||
|
||||
x: DefaultNamedArg(name="name")
|
||||
|
||||
x: NamedTuple("X", [("foo",), ("bar",)])
|
||||
|
||||
x: NamedTuple("X", ["foo", "bar"])
|
||||
|
||||
x: NamedTuple()
|
||||
|
||||
x: Literal["foo", "bar"]
|
||||
|
||||
x = cast(x, "str")
|
||||
|
||||
|
||||
def foo(x, *args, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
def foo(*, inplace):
|
||||
...
|
||||
|
||||
|
||||
x: Annotated[1:2] = ...
|
||||
|
||||
x = TypeVar("x", "str", "int")
|
||||
|
||||
x = cast("str", x)
|
||||
|
||||
X = List["MyClass"]
|
||||
16
crates/ruff/resources/test/fixtures/pyupgrade/UP040.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/pyupgrade/UP040.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import typing
|
||||
from typing import TypeAlias
|
||||
|
||||
# UP040
|
||||
x: typing.TypeAlias = int
|
||||
x: TypeAlias = int
|
||||
|
||||
|
||||
# UP040 with generics (todo)
|
||||
T = typing.TypeVar["T"]
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
# OK
|
||||
x: TypeAlias
|
||||
x: int = 1
|
||||
@@ -11,6 +11,8 @@ class A:
|
||||
without_annotation = []
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
class_variable_without_subscript: ClassVar = []
|
||||
final_variable_without_subscript: Final = []
|
||||
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@@ -2,21 +2,9 @@ x = range(10)
|
||||
|
||||
# RUF015
|
||||
list(x)[0]
|
||||
list(x)[:1]
|
||||
list(x)[:1:1]
|
||||
list(x)[:1:2]
|
||||
tuple(x)[0]
|
||||
tuple(x)[:1]
|
||||
tuple(x)[:1:1]
|
||||
tuple(x)[:1:2]
|
||||
list(i for i in x)[0]
|
||||
list(i for i in x)[:1]
|
||||
list(i for i in x)[:1:1]
|
||||
list(i for i in x)[:1:2]
|
||||
[i for i in x][0]
|
||||
[i for i in x][:1]
|
||||
[i for i in x][:1:1]
|
||||
[i for i in x][:1:2]
|
||||
|
||||
# OK (not indexing (solely) the first element)
|
||||
list(x)
|
||||
@@ -29,6 +17,9 @@ list(x)[::]
|
||||
[i for i in x]
|
||||
[i for i in x][1]
|
||||
[i for i in x][-1]
|
||||
[i for i in x][:1]
|
||||
[i for i in x][:1:1]
|
||||
[i for i in x][:1:2]
|
||||
[i for i in x][1:]
|
||||
[i for i in x][:3:2]
|
||||
[i for i in x][::2]
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/ruff/ruff_noqa_invalid.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/ruff/ruff_noqa_invalid.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import os # ruff: noqa: F401
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
@@ -4,9 +4,10 @@ use anyhow::{bail, Result};
|
||||
use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_trivia::{is_python_whitespace, NewlineWithTrailingNewline, PythonWhitespace};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::autofix::codemods;
|
||||
|
||||
@@ -41,11 +43,9 @@ pub(crate) fn delete_stmt(
|
||||
if let Some(semicolon) = trailing_semicolon(stmt.end(), locator) {
|
||||
let next = next_stmt_break(semicolon, locator);
|
||||
Edit::deletion(stmt.start(), next)
|
||||
} else if helpers::has_leading_content(stmt.start(), locator) {
|
||||
} else if has_leading_content(stmt.start(), locator) {
|
||||
Edit::range_deletion(stmt.range())
|
||||
} else if let Some(start) =
|
||||
helpers::preceded_by_continuations(stmt.start(), locator, indexer)
|
||||
{
|
||||
} else if let Some(start) = indexer.preceded_by_continuations(stmt.start(), locator) {
|
||||
Edit::range_deletion(TextRange::new(start, stmt.end()))
|
||||
} else {
|
||||
let range = locator.full_lines_range(stmt.range());
|
||||
@@ -69,105 +69,101 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Parentheses {
|
||||
/// Remove parentheses, if the removed argument is the only argument left.
|
||||
Remove,
|
||||
/// Preserve parentheses, even if the removed argument is the only argument
|
||||
Preserve,
|
||||
}
|
||||
|
||||
/// Generic function to remove arguments or keyword arguments in function
|
||||
/// calls and class definitions. (For classes `args` should be considered
|
||||
/// `bases`)
|
||||
///
|
||||
/// Supports the removal of parentheses when this is the only (kw)arg left.
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub(crate) fn remove_argument(
|
||||
pub(crate) fn remove_argument<T: Ranged>(
|
||||
argument: &T,
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
locator: &Locator,
|
||||
call_at: TextSize,
|
||||
expr_range: TextRange,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
remove_parentheses: bool,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
let contents = locator.after(call_at);
|
||||
if arguments.keywords.len() + arguments.args.len() > 1 {
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let n_arguments = keywords.len() + args.len();
|
||||
if n_arguments == 0 {
|
||||
bail!("No arguments or keywords to remove");
|
||||
}
|
||||
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if tok.is_lpar() {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
range.start()
|
||||
} else {
|
||||
range.start() + TextSize::from(1)
|
||||
});
|
||||
}
|
||||
count = count.saturating_add(1);
|
||||
}
|
||||
|
||||
if tok.is_rpar() {
|
||||
count = count.saturating_sub(1);
|
||||
if count == 0 {
|
||||
fix_end = Some(if remove_parentheses {
|
||||
if arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(arguments.keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > argument.start())
|
||||
{
|
||||
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
|
||||
// argument to the end of the subsequent comma.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if seen_comma {
|
||||
if tok.is_non_logical_newline() {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if tok.is_newline() {
|
||||
range.end()
|
||||
} else {
|
||||
range.end() - TextSize::from(1)
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == argument.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && tok.is_comma() {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 2: argument or keyword is the last node, so delete from the start of the
|
||||
// previous comma to the end of the argument.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if range.start() == argument.start() {
|
||||
fix_end = Some(argument.end());
|
||||
break;
|
||||
}
|
||||
if tok.is_comma() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > expr_range.start())
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if tok.is_non_logical_newline() {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if tok.is_newline() {
|
||||
range.end()
|
||||
} else {
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == expr_range.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && tok.is_comma() {
|
||||
seen_comma = true;
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if range.start() == expr_range.start() {
|
||||
fix_end = Some(expr_range.end());
|
||||
break;
|
||||
// Only one argument; remove it (but preserve parentheses, if needed).
|
||||
Ok(match parentheses {
|
||||
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
|
||||
Parentheses::Preserve => {
|
||||
Edit::replacement("()".to_string(), arguments.start(), arguments.end())
|
||||
}
|
||||
if tok.is_comma() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,24 +291,24 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser::ast::{Ranged, Suite};
|
||||
use rustpython_parser::Parse;
|
||||
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
let contents = "x = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt.end(), &locator), None);
|
||||
|
||||
let contents = "x = 1; y = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
@@ -321,7 +317,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let contents = "x = 1 ; y = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
@@ -334,7 +330,7 @@ x = 1 \
|
||||
; y = 1
|
||||
"
|
||||
.trim();
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use itertools::Itertools;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nohash_hasher::IntSet;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMap;
|
||||
use crate::linter::FixTable;
|
||||
@@ -47,7 +46,7 @@ fn apply_fixes<'a>(
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Option<TextSize> = None;
|
||||
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
|
||||
let mut isolated: IntSet<u32> = IntSet::default();
|
||||
let mut isolated: FxHashSet<u32> = FxHashSet::default();
|
||||
let mut fixed = FxHashMap::default();
|
||||
let mut source_map = SourceMap::default();
|
||||
|
||||
@@ -60,35 +59,30 @@ fn apply_fixes<'a>(
|
||||
})
|
||||
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
|
||||
{
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if fix.edits().iter().all(|edit| applied.contains(edit)) {
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
continue;
|
||||
}
|
||||
let mut edits = fix
|
||||
.edits()
|
||||
.iter()
|
||||
.filter(|edit| !applied.contains(edit))
|
||||
.peekable();
|
||||
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied,
|
||||
// skip it.
|
||||
if last_pos.map_or(false, |last_pos| {
|
||||
fix.min_start()
|
||||
.map_or(false, |fix_location| last_pos >= fix_location)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
// If the fix contains at least one new edit, enforce isolation and positional requirements.
|
||||
if let Some(first) = edits.peek() {
|
||||
// If this fix requires isolation, and we've already applied another fix in the
|
||||
// same isolation group, skip it.
|
||||
if let IsolationLevel::Group(id) = fix.isolation() {
|
||||
if !isolated.insert(id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If this fix requires isolation, and we've already applied another fix in the
|
||||
// same isolation group, skip it.
|
||||
if let IsolationLevel::Group(id) = fix.isolation() {
|
||||
if !isolated.insert(id) {
|
||||
// If this fix overlaps with a fix we've already applied, skip it.
|
||||
if last_pos.is_some_and(|last_pos| last_pos >= first.start()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for edit in fix
|
||||
.edits()
|
||||
.iter()
|
||||
.sorted_unstable_by_key(|edit| edit.start())
|
||||
{
|
||||
let mut applied_edits = Vec::with_capacity(fix.edits().len());
|
||||
for edit in edits {
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(TextRange::new(last_pos.unwrap_or_default(), edit.start()));
|
||||
output.push_str(slice);
|
||||
@@ -104,9 +98,10 @@ fn apply_fixes<'a>(
|
||||
|
||||
// Track that the edit was applied.
|
||||
last_pos = Some(edit.end());
|
||||
applied.insert(edit);
|
||||
applied_edits.push(edit);
|
||||
}
|
||||
|
||||
applied.extend(applied_edits.drain(..));
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
}
|
||||
|
||||
@@ -147,7 +142,7 @@ mod tests {
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMarker;
|
||||
use crate::autofix::{apply_fixes, FixResult};
|
||||
|
||||
67
crates/ruff/src/checkers/ast/analyze/bindings.rs
Normal file
67
crates/ruff/src/checkers/ast/analyze/bindings.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint};
|
||||
|
||||
/// Run lint rules over the [`Binding`]s.
|
||||
pub(crate) fn bindings(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::InvalidAllFormat,
|
||||
Rule::InvalidAllObject,
|
||||
Rule::UnaliasedCollectionsAbcSetImport,
|
||||
Rule::UnconventionalImportAlias,
|
||||
Rule::UnusedVariable,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for binding in checker.semantic.bindings.iter() {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
if binding.kind.is_bound_exception() && !binding.is_used() {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::UnusedVariable {
|
||||
name: binding.name(checker.locator).to_string(),
|
||||
},
|
||||
binding.range,
|
||||
);
|
||||
if checker.patch(Rule::UnusedVariable) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
pyflakes::fixes::remove_exception_handler_assignment(
|
||||
binding,
|
||||
checker.locator,
|
||||
)
|
||||
.map(Fix::automatic)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::InvalidAllFormat) {
|
||||
if let Some(diagnostic) = pylint::rules::invalid_all_format(binding) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::InvalidAllObject) {
|
||||
if let Some(diagnostic) = pylint::rules::invalid_all_object(binding) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnconventionalImportAlias) {
|
||||
if let Some(diagnostic) = flake8_import_conventions::rules::unconventional_import_alias(
|
||||
checker,
|
||||
binding,
|
||||
&checker.settings.flake8_import_conventions.aliases,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
crates/ruff/src/checkers/ast/analyze/comprehension.rs
Normal file
16
crates/ruff/src/checkers/ast/analyze/comprehension.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use ruff_python_ast::Comprehension;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::flake8_simplify;
|
||||
|
||||
/// Run lint rules over a [`Comprehension`] syntax nodes.
|
||||
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_for(
|
||||
checker,
|
||||
&comprehension.target,
|
||||
&comprehension.iter,
|
||||
);
|
||||
}
|
||||
}
|
||||
32
crates/ruff/src/checkers/ast/analyze/deferred_for_loops.rs
Normal file
32
crates/ruff/src/checkers/ast/analyze/deferred_for_loops.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bugbear, perflint};
|
||||
|
||||
/// Run lint rules over all deferred for-loops in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
while !checker.deferred.for_loops.is_empty() {
|
||||
let for_loops = std::mem::take(&mut checker.deferred.for_loops);
|
||||
for snapshot in for_loops {
|
||||
checker.semantic.restore(snapshot);
|
||||
|
||||
if let Stmt::For(ast::StmtFor {
|
||||
target, iter, body, ..
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target, iter, body, ..
|
||||
}) = &checker.semantic.stmt()
|
||||
{
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
}
|
||||
} else {
|
||||
unreachable!("Expected Expr::For | Expr::AsyncFor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
304
crates/ruff/src/checkers/ast/analyze/deferred_scopes.rs
Normal file
304
crates/ruff/src/checkers/ast/analyze/deferred_scopes.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint};
|
||||
|
||||
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::GlobalVariableNotAssigned,
|
||||
Rule::ImportShadowedByLoopVar,
|
||||
Rule::RedefinedWhileUnused,
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
Rule::UndefinedLocal,
|
||||
Rule::UnusedAnnotation,
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::UnusedFunctionArgument,
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedLambdaArgument,
|
||||
Rule::UnusedMethodArgument,
|
||||
Rule::UnusedPrivateProtocol,
|
||||
Rule::UnusedPrivateTypeAlias,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||
// used at runtime, then by default, we avoid flagging any other
|
||||
// imports from that model as typing-only.
|
||||
let enforce_typing_imports = !checker.is_stub
|
||||
&& checker.any_enabled(&[
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
]);
|
||||
let runtime_imports: Vec<Vec<&Binding>> = if enforce_typing_imports {
|
||||
checker
|
||||
.semantic
|
||||
.scopes
|
||||
.iter()
|
||||
.map(|scope| {
|
||||
scope
|
||||
.binding_ids()
|
||||
.map(|binding_id| checker.semantic.binding(binding_id))
|
||||
.filter(|binding| {
|
||||
flake8_type_checking::helpers::is_valid_runtime_import(
|
||||
binding,
|
||||
&checker.semantic,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
for scope_id in checker.deferred.scopes.iter().rev().copied() {
|
||||
let scope = &checker.semantic.scopes[scope_id];
|
||||
|
||||
if checker.enabled(Rule::UndefinedLocal) {
|
||||
pyflakes::rules::undefined_local(checker, scope_id, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::GlobalVariableNotAssigned) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
let binding = checker.semantic.binding(binding_id);
|
||||
if binding.kind.is_global() {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pylint::rules::GlobalVariableNotAssigned {
|
||||
name: (*name).to_string(),
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::ImportShadowedByLoopVar) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
// If the shadowing binding isn't a loop variable, abort.
|
||||
let binding = &checker.semantic.bindings[shadow.binding_id()];
|
||||
if !binding.kind.is_loop_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowed binding isn't an import, abort.
|
||||
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportShadowedByLoopVar {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::RedefinedWhileUnused) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
// If the shadowing binding is a loop variable, abort, to avoid overlap
|
||||
// with F402.
|
||||
let binding = &checker.semantic.bindings[shadow.binding_id()];
|
||||
if binding.kind.is_loop_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowed binding is used, abort.
|
||||
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
|
||||
if shadowed.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowing binding isn't considered a "redefinition" of the
|
||||
// shadowed binding, abort.
|
||||
if !binding.redefines(shadowed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if shadow.same_scope() {
|
||||
// If the symbol is a dummy variable, abort, unless the shadowed
|
||||
// binding is an import.
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) && checker.settings.dummy_variable_rgx.is_match(name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is an overloaded function, abort.
|
||||
if shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(
|
||||
checker.semantic.stmts[shadowed.source.unwrap()],
|
||||
),
|
||||
&checker.semantic,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Only enforce cross-scope shadowing for imports.
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = checker.locator.compute_line_index(shadowed.range.start());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::RedefinedWhileUnused {
|
||||
name: (*name).to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
);
|
||||
if let Some(range) = binding.parent_range(&checker.semantic) {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
|
||||
) {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::UnusedAnnotation) {
|
||||
pyflakes::rules::unused_annotation(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if !checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::UnusedFunctionArgument,
|
||||
Rule::UnusedLambdaArgument,
|
||||
Rule::UnusedMethodArgument,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
]) {
|
||||
flake8_unused_arguments::rules::unused_arguments(
|
||||
checker,
|
||||
scope,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Module
|
||||
) {
|
||||
if enforce_typing_imports {
|
||||
let runtime_imports: Vec<&Binding> = checker
|
||||
.semantic
|
||||
.scopes
|
||||
.ancestor_ids(scope_id)
|
||||
.flat_map(|scope_id| runtime_imports[scope_id.as_usize()].iter())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
if checker.enabled(Rule::RuntimeImportInTypeCheckingBlock) {
|
||||
flake8_type_checking::rules::runtime_import_in_type_checking_block(
|
||||
checker,
|
||||
scope,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
]) {
|
||||
flake8_type_checking::rules::typing_only_runtime_import(
|
||||
checker,
|
||||
scope,
|
||||
&runtime_imports,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::UnusedImport) {
|
||||
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.diagnostics.extend(diagnostics);
|
||||
}
|
||||
293
crates/ruff/src/checkers/ast/analyze/definitions.rs
Normal file
293
crates/ruff/src/checkers/ast/analyze/definitions.rs
Normal file
@@ -0,0 +1,293 @@
|
||||
use ruff_python_ast::str::raw_contents_range;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_python_semantic::{BindingKind, ContextualizedDefinition, Export};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::docstrings::Docstring;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::rules::{flake8_annotations, flake8_pyi, pydocstyle};
|
||||
use crate::{docstrings, warn_user};
|
||||
|
||||
/// Run lint rules over all [`Definition`] nodes in the [`SemanticModel`].
|
||||
///
|
||||
/// This phase is expected to run after the AST has been traversed in its entirety; as such,
|
||||
/// it is expected that all [`Definition`] nodes have been visited by the time, and that this
|
||||
/// method will not recurse into any other nodes.
|
||||
pub(crate) fn definitions(checker: &mut Checker) {
|
||||
let enforce_annotations = checker.any_enabled(&[
|
||||
Rule::AnyType,
|
||||
Rule::MissingReturnTypeClassMethod,
|
||||
Rule::MissingReturnTypePrivateFunction,
|
||||
Rule::MissingReturnTypeSpecialMethod,
|
||||
Rule::MissingReturnTypeStaticMethod,
|
||||
Rule::MissingReturnTypeUndocumentedPublicFunction,
|
||||
Rule::MissingTypeArgs,
|
||||
Rule::MissingTypeCls,
|
||||
Rule::MissingTypeFunctionArgument,
|
||||
Rule::MissingTypeKwargs,
|
||||
Rule::MissingTypeSelf,
|
||||
]);
|
||||
let enforce_stubs = checker.is_stub && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
|
||||
let enforce_docstrings = checker.any_enabled(&[
|
||||
Rule::BlankLineAfterLastSection,
|
||||
Rule::BlankLineAfterSummary,
|
||||
Rule::BlankLineBeforeClass,
|
||||
Rule::BlankLinesBetweenHeaderAndContent,
|
||||
Rule::CapitalizeSectionName,
|
||||
Rule::DashedUnderlineAfterSection,
|
||||
Rule::DocstringStartsWithThis,
|
||||
Rule::EmptyDocstring,
|
||||
Rule::EmptyDocstringSection,
|
||||
Rule::EndsInPeriod,
|
||||
Rule::EndsInPunctuation,
|
||||
Rule::EscapeSequenceInDocstring,
|
||||
Rule::FirstLineCapitalized,
|
||||
Rule::FitsOnOneLine,
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
Rule::MultiLineSummarySecondLine,
|
||||
Rule::NewLineAfterLastParagraph,
|
||||
Rule::NewLineAfterSectionName,
|
||||
Rule::NoBlankLineAfterFunction,
|
||||
Rule::NoBlankLineAfterSection,
|
||||
Rule::NoBlankLineBeforeFunction,
|
||||
Rule::NoBlankLineBeforeSection,
|
||||
Rule::NoSignature,
|
||||
Rule::NonImperativeMood,
|
||||
Rule::OneBlankLineAfterClass,
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
Rule::OverIndentation,
|
||||
Rule::OverloadWithDocstring,
|
||||
Rule::SectionNameEndsInColon,
|
||||
Rule::SectionNotOverIndented,
|
||||
Rule::SectionUnderlineAfterName,
|
||||
Rule::SectionUnderlineMatchesSectionLength,
|
||||
Rule::SectionUnderlineNotOverIndented,
|
||||
Rule::SurroundingWhitespace,
|
||||
Rule::TripleSingleQuotes,
|
||||
Rule::UnderIndentation,
|
||||
Rule::UndocumentedMagicMethod,
|
||||
Rule::UndocumentedParam,
|
||||
Rule::UndocumentedPublicClass,
|
||||
Rule::UndocumentedPublicFunction,
|
||||
Rule::UndocumentedPublicInit,
|
||||
Rule::UndocumentedPublicMethod,
|
||||
Rule::UndocumentedPublicModule,
|
||||
Rule::UndocumentedPublicNestedClass,
|
||||
Rule::UndocumentedPublicPackage,
|
||||
]);
|
||||
|
||||
if !enforce_annotations && !enforce_docstrings && !enforce_stubs && !enforce_stubs_and_runtime {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute visibility of all definitions.
|
||||
let exports: Option<Vec<&str>> = {
|
||||
checker
|
||||
.semantic
|
||||
.global_scope()
|
||||
.get_all("__all__")
|
||||
.map(|binding_id| &checker.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
|
||||
_ => None,
|
||||
})
|
||||
.fold(None, |acc, names| {
|
||||
Some(acc.into_iter().flatten().chain(names).collect())
|
||||
})
|
||||
};
|
||||
|
||||
let definitions = std::mem::take(&mut checker.semantic.definitions);
|
||||
let mut overloaded_name: Option<String> = None;
|
||||
for ContextualizedDefinition {
|
||||
definition,
|
||||
visibility,
|
||||
} in definitions.resolve(exports.as_deref()).iter()
|
||||
{
|
||||
let docstring = docstrings::extraction::extract_docstring(definition);
|
||||
|
||||
// flake8-annotations
|
||||
if enforce_annotations {
|
||||
// TODO(charlie): This should be even stricter, in that an overload
|
||||
// implementation should come immediately after the overloaded
|
||||
// interfaces, without any AST nodes in between. Right now, we
|
||||
// only error when traversing definition boundaries (functions,
|
||||
// classes, etc.).
|
||||
if !overloaded_name.is_some_and(|overloaded_name| {
|
||||
flake8_annotations::helpers::is_overload_impl(
|
||||
definition,
|
||||
&overloaded_name,
|
||||
&checker.semantic,
|
||||
)
|
||||
}) {
|
||||
checker
|
||||
.diagnostics
|
||||
.extend(flake8_annotations::rules::definition(
|
||||
checker,
|
||||
definition,
|
||||
*visibility,
|
||||
));
|
||||
}
|
||||
overloaded_name =
|
||||
flake8_annotations::helpers::overloaded_name(definition, &checker.semantic);
|
||||
}
|
||||
|
||||
// flake8-pyi
|
||||
if enforce_stubs {
|
||||
if checker.enabled(Rule::DocstringInStub) {
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, docstring);
|
||||
}
|
||||
}
|
||||
if enforce_stubs_and_runtime {
|
||||
if checker.enabled(Rule::IterMethodReturnIterable) {
|
||||
flake8_pyi::rules::iter_method_return_iterable(checker, definition);
|
||||
}
|
||||
}
|
||||
|
||||
// pydocstyle
|
||||
if enforce_docstrings {
|
||||
if pydocstyle::helpers::should_ignore_definition(
|
||||
definition,
|
||||
&checker.settings.pydocstyle.ignore_decorators,
|
||||
&checker.semantic,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract a `Docstring` from a `Definition`.
|
||||
let Some(expr) = docstring else {
|
||||
pydocstyle::rules::not_missing(checker, definition, *visibility);
|
||||
continue;
|
||||
};
|
||||
|
||||
let contents = checker.locator.slice(expr.range());
|
||||
|
||||
let indentation = checker.locator.slice(TextRange::new(
|
||||
checker.locator.line_start(expr.start()),
|
||||
expr.start(),
|
||||
));
|
||||
|
||||
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
||||
#[allow(deprecated)]
|
||||
let location = checker.locator.compute_source_location(expr.start());
|
||||
warn_user!(
|
||||
"Docstring at {}:{}:{} contains implicit string concatenation; ignoring...",
|
||||
relativize_path(checker.path),
|
||||
location.row,
|
||||
location.column
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// SAFETY: Safe for docstrings that pass `should_ignore_docstring`.
|
||||
let body_range = raw_contents_range(contents).unwrap();
|
||||
let docstring = Docstring {
|
||||
definition,
|
||||
expr,
|
||||
contents,
|
||||
body_range,
|
||||
indentation,
|
||||
};
|
||||
|
||||
if !pydocstyle::rules::not_empty(checker, &docstring) {
|
||||
continue;
|
||||
}
|
||||
if checker.enabled(Rule::FitsOnOneLine) {
|
||||
pydocstyle::rules::one_liner(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::NoBlankLineAfterFunction,
|
||||
Rule::NoBlankLineBeforeFunction,
|
||||
]) {
|
||||
pydocstyle::rules::blank_before_after_function(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::BlankLineBeforeClass,
|
||||
Rule::OneBlankLineAfterClass,
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
]) {
|
||||
pydocstyle::rules::blank_before_after_class(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::BlankLineAfterSummary) {
|
||||
pydocstyle::rules::blank_after_summary(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::OverIndentation,
|
||||
Rule::UnderIndentation,
|
||||
]) {
|
||||
pydocstyle::rules::indent(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::NewLineAfterLastParagraph) {
|
||||
pydocstyle::rules::newline_after_last_paragraph(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::SurroundingWhitespace) {
|
||||
pydocstyle::rules::no_surrounding_whitespace(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
Rule::MultiLineSummarySecondLine,
|
||||
]) {
|
||||
pydocstyle::rules::multi_line_summary_start(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::TripleSingleQuotes) {
|
||||
pydocstyle::rules::triple_quotes(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::EscapeSequenceInDocstring) {
|
||||
pydocstyle::rules::backslashes(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::EndsInPeriod) {
|
||||
pydocstyle::rules::ends_with_period(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::NonImperativeMood) {
|
||||
pydocstyle::rules::non_imperative_mood(
|
||||
checker,
|
||||
&docstring,
|
||||
&checker.settings.pydocstyle.property_decorators,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::NoSignature) {
|
||||
pydocstyle::rules::no_signature(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::FirstLineCapitalized) {
|
||||
pydocstyle::rules::capitalized(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::DocstringStartsWithThis) {
|
||||
pydocstyle::rules::starts_with_this(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::EndsInPunctuation) {
|
||||
pydocstyle::rules::ends_with_punctuation(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::OverloadWithDocstring) {
|
||||
pydocstyle::rules::if_needed(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::BlankLineAfterLastSection,
|
||||
Rule::BlankLinesBetweenHeaderAndContent,
|
||||
Rule::CapitalizeSectionName,
|
||||
Rule::DashedUnderlineAfterSection,
|
||||
Rule::EmptyDocstringSection,
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
Rule::NewLineAfterSectionName,
|
||||
Rule::NoBlankLineAfterSection,
|
||||
Rule::NoBlankLineBeforeSection,
|
||||
Rule::SectionNameEndsInColon,
|
||||
Rule::SectionNotOverIndented,
|
||||
Rule::SectionUnderlineAfterName,
|
||||
Rule::SectionUnderlineMatchesSectionLength,
|
||||
Rule::SectionUnderlineNotOverIndented,
|
||||
Rule::UndocumentedParam,
|
||||
]) {
|
||||
pydocstyle::rules::sections(
|
||||
checker,
|
||||
&docstring,
|
||||
checker.settings.pydocstyle.convention.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
crates/ruff/src/checkers/ast/analyze/except_handler.rs
Normal file
88
crates/ruff/src/checkers/ast/analyze/except_handler.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
flake8_bandit, flake8_blind_except, flake8_bugbear, flake8_builtins, pycodestyle, pylint,
|
||||
tryceratops,
|
||||
};
|
||||
|
||||
/// Run lint rules over an [`ExceptHandler`] syntax node.
|
||||
pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &mut Checker) {
|
||||
match except_handler {
|
||||
ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
type_,
|
||||
name,
|
||||
body,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::BareExcept) {
|
||||
if let Some(diagnostic) = pycodestyle::rules::bare_except(
|
||||
type_.as_deref(),
|
||||
body,
|
||||
except_handler,
|
||||
checker.locator,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::RaiseWithoutFromInsideExcept) {
|
||||
flake8_bugbear::rules::raise_without_from_inside_except(
|
||||
checker,
|
||||
name.as_deref(),
|
||||
body,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BlindExcept) {
|
||||
flake8_blind_except::rules::blind_except(
|
||||
checker,
|
||||
type_.as_deref(),
|
||||
name.as_deref(),
|
||||
body,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::TryExceptPass) {
|
||||
flake8_bandit::rules::try_except_pass(
|
||||
checker,
|
||||
except_handler,
|
||||
type_.as_deref(),
|
||||
body,
|
||||
checker.settings.flake8_bandit.check_typed_exception,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::TryExceptContinue) {
|
||||
flake8_bandit::rules::try_except_continue(
|
||||
checker,
|
||||
except_handler,
|
||||
type_.as_deref(),
|
||||
body,
|
||||
checker.settings.flake8_bandit.check_typed_exception,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::ExceptWithEmptyTuple) {
|
||||
flake8_bugbear::rules::except_with_empty_tuple(checker, except_handler);
|
||||
}
|
||||
if checker.enabled(Rule::ExceptWithNonExceptionClasses) {
|
||||
flake8_bugbear::rules::except_with_non_exception_classes(checker, except_handler);
|
||||
}
|
||||
if checker.enabled(Rule::ReraiseNoCause) {
|
||||
tryceratops::rules::reraise_no_cause(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::BinaryOpException) {
|
||||
pylint::rules::binary_op_exception(checker, except_handler);
|
||||
}
|
||||
if let Some(name) = name {
|
||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_variable_name(name.as_str(), name.range())
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BuiltinVariableShadowing) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1385
crates/ruff/src/checkers/ast/analyze/expression.rs
Normal file
1385
crates/ruff/src/checkers/ast/analyze/expression.rs
Normal file
File diff suppressed because it is too large
Load Diff
27
crates/ruff/src/checkers/ast/analyze/mod.rs
Normal file
27
crates/ruff/src/checkers/ast/analyze/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub(super) use bindings::bindings;
|
||||
pub(super) use comprehension::comprehension;
|
||||
pub(super) use deferred_for_loops::deferred_for_loops;
|
||||
pub(super) use deferred_scopes::deferred_scopes;
|
||||
pub(super) use definitions::definitions;
|
||||
pub(super) use except_handler::except_handler;
|
||||
pub(super) use expression::expression;
|
||||
pub(super) use module::module;
|
||||
pub(super) use parameter::parameter;
|
||||
pub(super) use parameters::parameters;
|
||||
pub(super) use statement::statement;
|
||||
pub(super) use suite::suite;
|
||||
pub(super) use unresolved_references::unresolved_references;
|
||||
|
||||
mod bindings;
|
||||
mod comprehension;
|
||||
mod deferred_for_loops;
|
||||
mod deferred_scopes;
|
||||
mod definitions;
|
||||
mod except_handler;
|
||||
mod expression;
|
||||
mod module;
|
||||
mod parameter;
|
||||
mod parameters;
|
||||
mod statement;
|
||||
mod suite;
|
||||
mod unresolved_references;
|
||||
12
crates/ruff/src/checkers/ast/analyze/module.rs
Normal file
12
crates/ruff/src/checkers/ast/analyze/module.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use ruff_python_ast::Suite;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::flake8_bugbear;
|
||||
|
||||
/// Run lint rules over a module.
|
||||
pub(crate) fn module(suite: &Suite, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::FStringDocstring) {
|
||||
flake8_bugbear::rules::f_string_docstring(checker, suite);
|
||||
}
|
||||
}
|
||||
28
crates/ruff/src/checkers/ast/analyze/parameter.rs
Normal file
28
crates/ruff/src/checkers/ast/analyze/parameter.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use ruff_python_ast::{Parameter, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_builtins, pep8_naming, pycodestyle};
|
||||
|
||||
/// Run lint rules over a [`Parameter`] syntax node.
|
||||
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_variable_name(¶meter.name, parameter.range())
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::InvalidArgumentName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
|
||||
¶meter.name,
|
||||
parameter,
|
||||
&checker.settings.pep8_naming.ignore_names,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BuiltinArgumentShadowing) {
|
||||
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
|
||||
}
|
||||
}
|
||||
26
crates/ruff/src/checkers/ast/analyze/parameters.rs
Normal file
26
crates/ruff/src/checkers/ast/analyze/parameters.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use ruff_python_ast::Parameters;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
|
||||
|
||||
/// Run lint rules over a [`Parameters`] syntax node.
|
||||
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MutableArgumentDefault) {
|
||||
flake8_bugbear::rules::mutable_argument_default(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
|
||||
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
|
||||
ruff::rules::implicit_optional(checker, parameters);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::ArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
1457
crates/ruff/src/checkers/ast/analyze/statement.rs
Normal file
1457
crates/ruff/src/checkers/ast/analyze/statement.rs
Normal file
File diff suppressed because it is too large
Load Diff
12
crates/ruff/src/checkers/ast/analyze/suite.rs
Normal file
12
crates/ruff/src/checkers/ast/analyze/suite.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use ruff_python_ast::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::flake8_pie;
|
||||
|
||||
/// Run lint rules over a suite of [`Stmt`] syntax nodes.
|
||||
pub(crate) fn suite(suite: &[Stmt], checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryPass) {
|
||||
flake8_pie::rules::no_unnecessary_pass(checker, suite);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_semantic::Exceptions;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::pyflakes;
|
||||
|
||||
/// Run lint rules over all [`UnresolvedReference`] entities in the [`SemanticModel`].
|
||||
pub(crate) fn unresolved_references(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[Rule::UndefinedLocalWithImportStarUsage, Rule::UndefinedName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for reference in checker.semantic.unresolved_references() {
|
||||
if reference.is_wildcard_import() {
|
||||
if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: reference.name(checker.locator).to_string(),
|
||||
},
|
||||
reference.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if checker.enabled(Rule::UndefinedName) {
|
||||
// Avoid flagging if `NameError` is handled.
|
||||
if reference.exceptions().contains(Exceptions::NAME_ERROR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow __path__.
|
||||
if checker.path.ends_with("__init__.py") {
|
||||
if reference.name(checker.locator) == "__path__" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedName {
|
||||
name: reference.name(checker.locator).to_string(),
|
||||
},
|
||||
reference.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use ruff_python_ast::{Expr, TypeParam};
|
||||
use ruff_python_semantic::{ScopeId, Snapshot};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
/// A collection of AST nodes that are deferred for later analysis.
|
||||
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
||||
@@ -11,6 +10,7 @@ pub(crate) struct Deferred<'a> {
|
||||
pub(crate) scopes: Vec<ScopeId>,
|
||||
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) for_loops: Vec<Snapshot>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user