Compare commits
126 Commits
dylan/stab
...
0.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87f0feb21a | ||
|
|
685eac10e5 | ||
|
|
a93992fa30 | ||
|
|
50f84808bc | ||
|
|
6754e94abc | ||
|
|
33c8c7569d | ||
|
|
34dc8e0531 | ||
|
|
b01195b166 | ||
|
|
ce176b1acf | ||
|
|
7072cf69b4 | ||
|
|
72c8dc006f | ||
|
|
ad9ae4e2b6 | ||
|
|
de4fc5b171 | ||
|
|
56f2aaaebc | ||
|
|
ebd2a27559 | ||
|
|
1278e3442a | ||
|
|
7efbf469dd | ||
|
|
2a1fed9327 | ||
|
|
7de8a0b429 | ||
|
|
0a1c6cb70b | ||
|
|
2dafc5a8bd | ||
|
|
72a4c3ed83 | ||
|
|
e559e21e93 | ||
|
|
c948be495a | ||
|
|
cd245d292e | ||
|
|
620b84443b | ||
|
|
1f70ceba0c | ||
|
|
00e9de8db9 | ||
|
|
7211660f8b | ||
|
|
c1610e2eaf | ||
|
|
bf53bc4256 | ||
|
|
9f2ae1f568 | ||
|
|
5cf2c40d13 | ||
|
|
02b5376a3c | ||
|
|
18a134ae1f | ||
|
|
c063940d52 | ||
|
|
8aea383f29 | ||
|
|
913f136d33 | ||
|
|
c7e020df6b | ||
|
|
1d458d4314 | ||
|
|
342b2665db | ||
|
|
390918e790 | ||
|
|
a1c69ca460 | ||
|
|
3a77768f79 | ||
|
|
c22f809049 | ||
|
|
2b731d19b9 | ||
|
|
cff5adf324 | ||
|
|
7880a20794 | ||
|
|
83b0cde2fc | ||
|
|
373a3bfcd6 | ||
|
|
5e57e4680f | ||
|
|
2b15f1d240 | ||
|
|
c3aa965546 | ||
|
|
c5b58187da | ||
|
|
a842899862 | ||
|
|
ee3152dace | ||
|
|
869d7bf9a8 | ||
|
|
2f3bd24900 | ||
|
|
d715c1fef8 | ||
|
|
cb2ae8d9ac | ||
|
|
5383bcc497 | ||
|
|
9b927265f9 | ||
|
|
b38115ba95 | ||
|
|
32a0d4bb21 | ||
|
|
ccae65630a | ||
|
|
4cdf128748 | ||
|
|
0c18a5a737 | ||
|
|
37b2de90f8 | ||
|
|
3a430fa6da | ||
|
|
782363b736 | ||
|
|
8237d4670c | ||
|
|
5e02d839d5 | ||
|
|
e4423044f8 | ||
|
|
6d56ee803e | ||
|
|
89d915a1e3 | ||
|
|
1889a5e6eb | ||
|
|
793ff9bdbc | ||
|
|
c9dff5c7d5 | ||
|
|
76d9009a6e | ||
|
|
015222900f | ||
|
|
1f27d53fd5 | ||
|
|
3c6c017950 | ||
|
|
ef564094a9 | ||
|
|
96171f41c2 | ||
|
|
8123dab05a | ||
|
|
324e5cbc19 | ||
|
|
e6fe2af292 | ||
|
|
dbb0d60caa | ||
|
|
ef4108af2a | ||
|
|
f74527f4e9 | ||
|
|
65a2c6d4eb | ||
|
|
1a3befe8d6 | ||
|
|
7893cf9fe1 | ||
|
|
8fdf3fc47f | ||
|
|
65f32edbc7 | ||
|
|
e84406d8be | ||
|
|
a863000cbc | ||
|
|
3aae1cd59b | ||
|
|
5dcfc9f074 | ||
|
|
0724bee59c | ||
|
|
2213698a5d | ||
|
|
dc322d23dd | ||
|
|
a2de81cb27 | ||
|
|
eb60bd64fd | ||
|
|
b21ac567e1 | ||
|
|
6cd0669475 | ||
|
|
6051a118d1 | ||
|
|
161446a47a | ||
|
|
caf885c20a | ||
|
|
79006dfb52 | ||
|
|
b44062b9ae | ||
|
|
ae2150bfa3 | ||
|
|
07cb84426d | ||
|
|
b01c95d460 | ||
|
|
aa3c312f5f | ||
|
|
475a02b725 | ||
|
|
b4b53183b7 | ||
|
|
5fe6fa74a0 | ||
|
|
ea64c01524 | ||
|
|
3fa5a9ff3b | ||
|
|
b5a77df46f | ||
|
|
8d1d0be648 | ||
|
|
1cf7b67e85 | ||
|
|
c18dc41f1a | ||
|
|
6cefbb6b38 | ||
|
|
0232e422b2 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -5,6 +5,9 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf
|
||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
|
||||
|
||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py text eol=crlf
|
||||
crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap text eol=crlf
|
||||
|
||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
||||
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
||||
|
||||
|
||||
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@@ -237,7 +237,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1
|
||||
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
|
||||
with:
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1
|
||||
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
|
||||
with:
|
||||
@@ -380,7 +380,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1
|
||||
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
|
||||
- name: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
@@ -405,7 +405,7 @@ jobs:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: rustup default "${MSRV}"
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1
|
||||
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
|
||||
with:
|
||||
@@ -437,7 +437,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6
|
||||
uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -690,7 +690,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6
|
||||
- uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1
|
||||
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/sync_typeshed.yaml
vendored
2
.github/workflows/sync_typeshed.yaml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
cd ruff
|
||||
git push --force origin typeshedbot/sync-typeshed
|
||||
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "internal"
|
||||
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
|
||||
|
||||
create-issue-on-failure:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
|
||||
@@ -5,6 +5,7 @@ exclude: |
|
||||
.github/workflows/release.yml|
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty_python_semantic/resources/corpus/.*|
|
||||
crates/ty/docs/(configuration|rules|cli).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
@@ -66,7 +67,7 @@ repos:
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.32.0
|
||||
rev: v1.33.1
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -80,7 +81,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.12
|
||||
rev: v0.11.13
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- **Detection of more syntax errors**
|
||||
|
||||
Ruff now detects version-related syntax errors, such as the use of the `match`
|
||||
statement on Python versions before 3.10, and syntax errors emitted by
|
||||
CPython's compiler, such as irrefutable `match` patterns before the final
|
||||
`case` arm.
|
||||
|
||||
- **New default Python version handling for syntax errors**
|
||||
|
||||
Ruff will default to the _latest_ supported Python version (3.13) when
|
||||
checking for the version-related syntax errors mentioned above to prevent
|
||||
false positives in projects without a Python version configured. The default
|
||||
in all other cases, like applying lint rules, is unchanged and remains at the
|
||||
minimum supported Python version (3.9).
|
||||
|
||||
- **Updated f-string formatting**
|
||||
|
||||
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
|
||||
line break after the format specifier. This addresses a change to the Python
|
||||
grammar in version 3.13.4 that made such a line break a syntax error.
|
||||
|
||||
- **`rust-toolchain.toml` is no longer included in source distributions**
|
||||
|
||||
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
|
||||
minimum supported Rust version (MSRV) for development and building release
|
||||
artifacts. However, when present in source distributions, it would also cause
|
||||
downstream package maintainers to pull in the same Rust toolchain, even if
|
||||
their available toolchain was MSRV-compatible.
|
||||
|
||||
- **[`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
|
||||
(`S320`) has been removed**
|
||||
|
||||
## 0.11.0
|
||||
|
||||
This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`.
|
||||
|
||||
161
CHANGELOG.md
161
CHANGELOG.md
@@ -1,5 +1,145 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
|
||||
guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Detection of more syntax errors**
|
||||
|
||||
Ruff now detects version-related syntax errors, such as the use of the `match`
|
||||
statement on Python versions before 3.10, and syntax errors emitted by
|
||||
CPython's compiler, such as irrefutable `match` patterns before the final
|
||||
`case` arm.
|
||||
|
||||
- **New default Python version handling for syntax errors**
|
||||
|
||||
Ruff will default to the _latest_ supported Python version (3.13) when
|
||||
checking for the version-related syntax errors mentioned above to prevent
|
||||
false positives in projects without a Python version configured. The default
|
||||
in all other cases, like applying lint rules, is unchanged and remains at the
|
||||
minimum supported Python version (3.9).
|
||||
|
||||
- **Updated f-string formatting**
|
||||
|
||||
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
|
||||
line break after the format specifier. This addresses a change to the Python
|
||||
grammar in version 3.13.4 that made such a line break a syntax error.
|
||||
|
||||
- **`rust-toolchain.toml` is no longer included in source distributions**
|
||||
|
||||
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
|
||||
minimum supported Rust version (MSRV) for development and building release
|
||||
artifacts. However, when present in source distributions, it would also cause
|
||||
downstream package maintainers to pull in the same Rust toolchain, even if
|
||||
their available toolchain was MSRV-compatible.
|
||||
|
||||
### Removed Rules
|
||||
|
||||
The following rules have been removed:
|
||||
|
||||
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
|
||||
(`S320`)
|
||||
|
||||
### Deprecated Rules
|
||||
|
||||
The following rules have been deprecated:
|
||||
|
||||
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/)
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`)
|
||||
- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`)
|
||||
- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`)
|
||||
- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`)
|
||||
- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`)
|
||||
- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`)
|
||||
- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`)
|
||||
- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`)
|
||||
- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`)
|
||||
- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`)
|
||||
- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`)
|
||||
- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`)
|
||||
- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`)
|
||||
- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`)
|
||||
- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`)
|
||||
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
|
||||
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
|
||||
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
|
||||
- [`non-pep604-annotation-optional`](https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional) (`UP045`)
|
||||
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
|
||||
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
|
||||
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in
|
||||
addition to list literals and variables.
|
||||
- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe.
|
||||
- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify
|
||||
expressions to use `or` instead of an `if` expression, where possible.
|
||||
- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well
|
||||
as inline comments.
|
||||
- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings,
|
||||
as well as lists and tuples of literal strings, as trusted input.
|
||||
- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that
|
||||
include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to
|
||||
plain `bool` annotations.
|
||||
- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules.
|
||||
`UP007` now applies only to `typing.Union`, while
|
||||
[`non-pep604-annotation-optional`] (`UP045`) checks for use of
|
||||
`typing.Optional`. `UP045` has also been stabilized in this release, but you
|
||||
may need to update existing `include`, `ignore`, or `noqa` settings to
|
||||
accommodate this change.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368))
|
||||
- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706))
|
||||
- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542))
|
||||
- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673))
|
||||
- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704))
|
||||
- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611))
|
||||
- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428))
|
||||
- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472))
|
||||
- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479))
|
||||
- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688))
|
||||
- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592))
|
||||
- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614))
|
||||
- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598))
|
||||
- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631))
|
||||
- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509))
|
||||
- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408))
|
||||
- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532))
|
||||
- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311))
|
||||
- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545))
|
||||
- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478))
|
||||
- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645))
|
||||
|
||||
### Server
|
||||
|
||||
- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709))
|
||||
- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491))
|
||||
- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613))
|
||||
- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615))
|
||||
|
||||
## 0.11.13
|
||||
|
||||
### Preview features
|
||||
@@ -230,7 +370,7 @@
|
||||
- \[`airflow`\] Add missing `AIR302` attribute check ([#17115](https://github.com/astral-sh/ruff/pull/17115))
|
||||
- \[`airflow`\] Expand module path check to individual symbols (`AIR302`) ([#17278](https://github.com/astral-sh/ruff/pull/17278))
|
||||
- \[`airflow`\] Extract `AIR312` from `AIR302` rules (`AIR302`, `AIR312`) ([#17152](https://github.com/astral-sh/ruff/pull/17152))
|
||||
- \[`airflow`\] Update oudated `AIR301`, `AIR302` rules ([#17123](https://github.com/astral-sh/ruff/pull/17123))
|
||||
- \[`airflow`\] Update outdated `AIR301`, `AIR302` rules ([#17123](https://github.com/astral-sh/ruff/pull/17123))
|
||||
- [syntax-errors] Async comprehension in sync comprehension ([#17177](https://github.com/astral-sh/ruff/pull/17177))
|
||||
- [syntax-errors] Check annotations in annotated assignments ([#17283](https://github.com/astral-sh/ruff/pull/17283))
|
||||
- [syntax-errors] Extend annotation checks to `await` ([#17282](https://github.com/astral-sh/ruff/pull/17282))
|
||||
@@ -391,7 +531,7 @@ See also, the "Remapped rules" section which may result in disabled rules.
|
||||
|
||||
- **More robust noqa parsing** ([#16483](https://github.com/astral-sh/ruff/pull/16483))
|
||||
|
||||
The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [*Error suppression*](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification.
|
||||
The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [_Error suppression_](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification.
|
||||
|
||||
- **Avoid unnecessary parentheses around with statements with a single context manager and a trailing comment** ([#14005](https://github.com/astral-sh/ruff/pull/14005))
|
||||
|
||||
@@ -1313,7 +1453,7 @@ The following fixes have been stabilized:
|
||||
- Detect items that hash to same value in duplicate sets (`B033`, `PLC0208`) ([#14064](https://github.com/astral-sh/ruff/pull/14064))
|
||||
- \[`eradicate`\] Better detection of IntelliJ language injection comments (`ERA001`) ([#14094](https://github.com/astral-sh/ruff/pull/14094))
|
||||
- \[`flake8-pyi`\] Add autofix for `docstring-in-stub` (`PYI021`) ([#14150](https://github.com/astral-sh/ruff/pull/14150))
|
||||
- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to alawys provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188))
|
||||
- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to always provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188))
|
||||
- \[`pyflakes`\] Detect items that hash to same value in duplicate dictionaries (`F601`) ([#14065](https://github.com/astral-sh/ruff/pull/14065))
|
||||
- \[`ruff`\] Fix false positive for decorators (`RUF028`) ([#14061](https://github.com/astral-sh/ruff/pull/14061))
|
||||
|
||||
@@ -1803,7 +1943,7 @@ The following fixes have been stabilized:
|
||||
|
||||
## 0.5.6
|
||||
|
||||
Ruff 0.5.6 automatically enables linting and formatting of notebooks in *preview mode*.
|
||||
Ruff 0.5.6 automatically enables linting and formatting of notebooks in _preview mode_.
|
||||
You can opt-out of this behavior by adding `*.ipynb` to the `extend-exclude` setting.
|
||||
|
||||
```toml
|
||||
@@ -2556,7 +2696,7 @@ To setup `ruff server` with your editor, refer to the [README.md](https://github
|
||||
|
||||
### Server
|
||||
|
||||
*This section is devoted to updates for our new language server, written in Rust.*
|
||||
_This section is devoted to updates for our new language server, written in Rust._
|
||||
|
||||
- Enable ruff-specific source actions ([#10916](https://github.com/astral-sh/ruff/pull/10916))
|
||||
- Refreshes diagnostics for open files when file configuration is changed ([#10988](https://github.com/astral-sh/ruff/pull/10988))
|
||||
@@ -3963,7 +4103,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
|
||||
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
|
||||
|
||||
*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).*
|
||||
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -4038,3 +4178,12 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||
### Playground
|
||||
|
||||
- Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824))
|
||||
|
||||
[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument
|
||||
[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation
|
||||
[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp
|
||||
[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional
|
||||
[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union
|
||||
[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for
|
||||
[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true
|
||||
[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa
|
||||
|
||||
191
Cargo.lock
generated
191
Cargo.lock
generated
@@ -8,18 +8,6 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@@ -63,14 +51,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
version = "0.6.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -83,9 +71,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-lossy"
|
||||
@@ -124,7 +112,7 @@ dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
"html-escape",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -144,6 +132,12 @@ version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
version = "0.2.1"
|
||||
@@ -230,9 +224,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "boxcar"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66bb12751a83493ef4b8da1120451a262554e216a247f14b48cb5e8fe7ed8bdf"
|
||||
checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -268,9 +262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
||||
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -354,9 +348,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -364,9 +358,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -407,9 +401,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -534,7 +528,7 @@ dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -1118,16 +1112,12 @@ name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@@ -1140,7 +1130,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1338,7 +1328,7 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1358,7 +1348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1371,7 +1361,7 @@ dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
"vt100",
|
||||
"web-time",
|
||||
]
|
||||
@@ -1431,6 +1421,15 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
@@ -1514,9 +1513,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
|
||||
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -1529,9 +1528,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
|
||||
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1607,15 +1606,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.173"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.8.0"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ac076e37f8fe6bcddbb6c3282897e6e9498b254907ccbfc806dc8f9f1491f02"
|
||||
checksum = "ae28ddc5b90c3e3146a21d051ca095cbc8d932ad8714cf65ddf71a9abb35684c"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"libcst_derive",
|
||||
@@ -1628,9 +1627,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.8.0"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf4a12c744a301b216c4f0cb73542709ab15e6dadbb06966ac05864109d05da"
|
||||
checksum = "dc2de5c2f62bcf8a4f7290b1854388b262c4b68f1db1a3ee3ef6d4c1319b00a3"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1754,9 +1753,18 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
@@ -1944,6 +1952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d31b8b7a99f71bdff4235faf9ce9eada0ad3562c8fbeb7d607d9f41a6ec569d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2065,7 +2074,7 @@ checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
"unscanny",
|
||||
"version-ranges",
|
||||
]
|
||||
@@ -2086,7 +2095,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"version-ranges",
|
||||
@@ -2274,15 +2283,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.13.4"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643af57c3f36ba90a8b53e972727d8092f7408a9ebfbaf4c3d2c17b07c58d835"
|
||||
checksum = "7b0f6160dc48298b9260d9b958ad1d7f96f6cd0b9df200b22329204e09334663"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -2501,7 +2510,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.13"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2570,7 +2579,7 @@ dependencies = [
|
||||
"snapbox",
|
||||
"toml",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2610,6 +2619,7 @@ name = "ruff_db"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"arc-swap",
|
||||
"camino",
|
||||
"countme",
|
||||
"dashmap 6.1.0",
|
||||
@@ -2705,7 +2715,7 @@ dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2738,7 +2748,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.13"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -2794,7 +2804,7 @@ dependencies = [
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.2.1",
|
||||
"unicode_names2",
|
||||
"url",
|
||||
]
|
||||
@@ -2855,6 +2865,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3074,7 +3085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.11.13"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3194,16 +3205,16 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
"crossbeam-queue",
|
||||
"dashmap 6.1.0",
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown 0.15.3",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.15.4",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"intrusive-collections",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
@@ -3218,12 +3229,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3343,9 +3354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3431,9 +3442,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
@@ -3515,9 +3526,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
version = "2.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3740,9 +3751,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.22"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3752,18 +3763,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.9"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.26"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -3775,9 +3786,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -3933,12 +3944,17 @@ name = "ty_project"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"crossbeam",
|
||||
"glob",
|
||||
"globset",
|
||||
"insta",
|
||||
"notify",
|
||||
"ordermap",
|
||||
"pep440_rs",
|
||||
"rayon",
|
||||
"regex",
|
||||
"regex-automata 0.4.9",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
@@ -3970,7 +3986,8 @@ dependencies = [
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"hashbrown 0.15.3",
|
||||
"glob",
|
||||
"hashbrown 0.15.4",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
@@ -4001,6 +4018,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_test",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4036,6 +4054,7 @@ name = "ty_test"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.1",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
@@ -4183,9 +4202,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
|
||||
@@ -51,6 +51,7 @@ aho-corasick = { version = "1.1.3" }
|
||||
anstream = { version = "0.6.18" }
|
||||
anstyle = { version = "1.0.10" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
arc-swap = { version = "1.7.1" }
|
||||
assert_fs = { version = "1.1.0" }
|
||||
argfile = { version = "0.2.0" }
|
||||
bincode = { version = "2.0.0" }
|
||||
@@ -126,10 +127,11 @@ quote = { version = "1.0.23" }
|
||||
rand = { version = "0.9.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "09627e450566f894956710a3fd923dc80462ae6d" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.11.13/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.13/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.0/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.11.13
|
||||
rev: v0.12.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.11.13"
|
||||
version = "0.12.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::ExitStatus;
|
||||
use anyhow::Result;
|
||||
use ruff_server::Server;
|
||||
|
||||
pub(crate) fn run_server(
|
||||
worker_threads: NonZeroUsize,
|
||||
preview: Option<bool>,
|
||||
) -> Result<ExitStatus> {
|
||||
let server = Server::new(worker_threads, preview)?;
|
||||
|
||||
server.run().map(|()| ExitStatus::Success)
|
||||
pub(crate) fn run_server(preview: Option<bool>) -> Result<ExitStatus> {
|
||||
ruff_server::run(preview)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter, Write, stdout};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
@@ -223,13 +222,7 @@ fn analyze_graph(
|
||||
}
|
||||
|
||||
fn server(args: ServerCommand) -> Result<ExitStatus> {
|
||||
let four = NonZeroUsize::new(4).unwrap();
|
||||
|
||||
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
|
||||
let worker_threads = std::thread::available_parallelism()
|
||||
.unwrap_or(four)
|
||||
.min(four);
|
||||
commands::server::run_server(worker_threads, args.resolve_preview())
|
||||
commands::server::run_server(args.resolve_preview())
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||
|
||||
@@ -5436,14 +5436,15 @@ match 2:
|
||||
print("it's one")
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
// syntax error on 3.9 with preview
|
||||
|
||||
@@ -21,6 +21,7 @@ ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
anstyle = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
camino = { workspace = true }
|
||||
countme = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
|
||||
@@ -665,6 +665,76 @@ pub enum DiagnosticId {
|
||||
|
||||
/// No rule with the given name exists.
|
||||
UnknownRule,
|
||||
|
||||
/// A glob pattern doesn't follow the expected syntax.
|
||||
InvalidGlob,
|
||||
|
||||
/// An `include` glob without any patterns.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// An `include` glob without any patterns won't match any files. This is probably a mistake and
|
||||
/// either the `include` should be removed or a pattern should be added.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```toml
|
||||
/// [src]
|
||||
/// include = []
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```toml
|
||||
/// [src]
|
||||
/// include = ["src"]
|
||||
/// ```
|
||||
///
|
||||
/// or remove the `include` option.
|
||||
EmptyInclude,
|
||||
|
||||
/// An override configuration is unnecessary because it applies to all files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// An overrides section that applies to all files is probably a mistake and can be rolled-up into the root configuration.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```toml
|
||||
/// [[overrides]]
|
||||
/// [overrides.rules]
|
||||
/// unused-reference = "ignore"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```toml
|
||||
/// [rules]
|
||||
/// unused-reference = "ignore"
|
||||
/// ```
|
||||
///
|
||||
/// or
|
||||
///
|
||||
/// ```toml
|
||||
/// [[overrides]]
|
||||
/// include = ["test"]
|
||||
///
|
||||
/// [overrides.rules]
|
||||
/// unused-reference = "ignore"
|
||||
/// ```
|
||||
UnnecessaryOverridesSection,
|
||||
|
||||
/// An `overrides` section in the configuration that doesn't contain any overrides.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// An `overrides` section without any configuration overrides is probably a mistake.
|
||||
/// It is either a leftover after removing overrides, or a user forgot to add any overrides,
|
||||
/// or used an incorrect syntax to do so (e.g. used `rules` instead of `overrides.rules`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```toml
|
||||
/// [[overrides]]
|
||||
/// include = ["test"]
|
||||
/// # no `[overrides.rules]`
|
||||
/// ```
|
||||
UselessOverridesSection,
|
||||
}
|
||||
|
||||
impl DiagnosticId {
|
||||
@@ -699,6 +769,10 @@ impl DiagnosticId {
|
||||
DiagnosticId::Lint(name) => name.as_str(),
|
||||
DiagnosticId::RevealedType => "revealed-type",
|
||||
DiagnosticId::UnknownRule => "unknown-rule",
|
||||
DiagnosticId::InvalidGlob => "invalid-glob",
|
||||
DiagnosticId::EmptyInclude => "empty-include",
|
||||
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
|
||||
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.get();
|
||||
// The boundary case here is when `prev_context_ends`
|
||||
// is exactly one less than `this_context_begins`. In
|
||||
// that case, the context windows are adajcent and we
|
||||
// that case, the context windows are adjacent and we
|
||||
// should fall through below to add this annotation to
|
||||
// the existing snippet.
|
||||
if this_context_begins.saturating_sub(prev_context_ends) > 1 {
|
||||
@@ -754,7 +754,7 @@ kangaroo
|
||||
static FRUITS: &str = "\
|
||||
apple
|
||||
banana
|
||||
cantelope
|
||||
cantaloupe
|
||||
lime
|
||||
orange
|
||||
pear
|
||||
@@ -1376,8 +1376,8 @@ watermelon
|
||||
|
|
||||
1 | apple
|
||||
2 | banana
|
||||
3 | cantelope
|
||||
| ^^^^^^^^^
|
||||
3 | cantaloupe
|
||||
| ^^^^^^^^^^
|
||||
4 | lime
|
||||
5 | orange
|
||||
|
|
||||
@@ -1479,8 +1479,8 @@ watermelon
|
||||
|
|
||||
1 | apple
|
||||
2 | banana
|
||||
3 | cantelope
|
||||
| ^^^^^^^^^
|
||||
3 | cantaloupe
|
||||
| ^^^^^^^^^^
|
||||
4 | lime
|
||||
5 | orange
|
||||
|
|
||||
@@ -1515,8 +1515,8 @@ watermelon
|
||||
|
|
||||
1 | apple
|
||||
2 | banana
|
||||
3 | cantelope
|
||||
| ^^^^^^^^^
|
||||
3 | cantaloupe
|
||||
| ^^^^^^^^^^
|
||||
4 | lime
|
||||
5 | orange
|
||||
|
|
||||
@@ -1562,8 +1562,8 @@ watermelon
|
||||
|
|
||||
1 | apple
|
||||
2 | banana
|
||||
3 | cantelope
|
||||
| ^^^^^^^^^
|
||||
3 | cantaloupe
|
||||
| ^^^^^^^^^^
|
||||
4 | lime
|
||||
5 | orange
|
||||
|
|
||||
@@ -2040,7 +2040,7 @@ watermelon
|
||||
1 | apple
|
||||
| ^^^^^ primary
|
||||
2 | banana
|
||||
3 | cantelope
|
||||
3 | cantaloupe
|
||||
|
|
||||
::: animals:1:1
|
||||
|
|
||||
|
||||
@@ -59,6 +59,13 @@ pub fn max_parallelism() -> NonZeroUsize {
|
||||
})
|
||||
}
|
||||
|
||||
/// Trait for types that can provide Rust documentation.
|
||||
///
|
||||
/// Use `derive(RustDoc)` to automatically implement this trait for types that have a static string documentation.
|
||||
pub trait RustDoc {
|
||||
fn rust_doc() -> &'static str;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruff_python_ast::ModModule;
|
||||
use arc_swap::ArcSwapOption;
|
||||
use ruff_python_ast::{AnyRootNodeRef, ModModule, NodeIndex};
|
||||
use ruff_python_parser::{ParseOptions, Parsed, parse_unchecked};
|
||||
|
||||
use crate::Db;
|
||||
@@ -23,16 +24,20 @@ use crate::source::source_text;
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
let parsed = parsed_module_impl(db, file);
|
||||
|
||||
ParsedModule::new(file, parsed)
|
||||
}
|
||||
|
||||
pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed<ModModule> {
|
||||
let source = source_text(db, file);
|
||||
let ty = file.source_type(db);
|
||||
|
||||
let target_version = db.python_version();
|
||||
let options = ParseOptions::from(ty).with_target_version(target_version);
|
||||
let parsed = parse_unchecked(&source, options)
|
||||
parse_unchecked(&source, options)
|
||||
.try_into_module()
|
||||
.expect("PySourceType always parses into a module");
|
||||
|
||||
ParsedModule::new(parsed)
|
||||
.expect("PySourceType always parses into a module")
|
||||
}
|
||||
|
||||
/// A wrapper around a parsed module.
|
||||
@@ -41,21 +46,58 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
/// is represented with the [`ParsedModuleRef`] type.
|
||||
#[derive(Clone)]
|
||||
pub struct ParsedModule {
|
||||
inner: Arc<Parsed<ModModule>>,
|
||||
file: File,
|
||||
inner: Arc<ArcSwapOption<indexed::IndexedModule>>,
|
||||
}
|
||||
|
||||
impl ParsedModule {
|
||||
pub fn new(parsed: Parsed<ModModule>) -> Self {
|
||||
pub fn new(file: File, parsed: Parsed<ModModule>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(parsed),
|
||||
file,
|
||||
inner: Arc::new(ArcSwapOption::new(Some(indexed::IndexedModule::new(
|
||||
parsed,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
/// Loads a reference to the parsed module.
|
||||
///
|
||||
/// Note that holding on to the reference will prevent garbage collection
|
||||
/// of the AST. This method will reparse the module if it has been collected.
|
||||
pub fn load(&self, db: &dyn Db) -> ParsedModuleRef {
|
||||
let parsed = match self.inner.load_full() {
|
||||
Some(parsed) => parsed,
|
||||
None => {
|
||||
// Re-parse the file.
|
||||
let parsed = indexed::IndexedModule::new(parsed_module_impl(db, self.file));
|
||||
tracing::debug!(
|
||||
"File `{}` was reparsed after being collected in the current Salsa revision",
|
||||
self.file.path(db)
|
||||
);
|
||||
|
||||
self.inner.store(Some(parsed.clone()));
|
||||
parsed
|
||||
}
|
||||
};
|
||||
|
||||
ParsedModuleRef {
|
||||
module: self.clone(),
|
||||
indexed: parsed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a reference to the parsed module.
|
||||
pub fn load(&self, _db: &dyn Db) -> ParsedModuleRef {
|
||||
ParsedModuleRef {
|
||||
module_ref: self.inner.clone(),
|
||||
}
|
||||
/// Clear the parsed module, dropping the AST once all references to it are dropped.
|
||||
pub fn clear(&self) {
|
||||
self.inner.store(None);
|
||||
}
|
||||
|
||||
/// Returns a pointer for this [`ParsedModule`].
|
||||
///
|
||||
/// The pointer uniquely identifies the module within the current Salsa revision,
|
||||
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
|
||||
pub fn as_ptr(&self) -> *const () {
|
||||
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
|
||||
// `Arc` within the `ArcSwap` may change.
|
||||
Arc::as_ptr(&self.inner).cast()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,16 +118,19 @@ impl Eq for ParsedModule {}
|
||||
/// Cheap cloneable wrapper around an instance of a module AST.
|
||||
#[derive(Clone)]
|
||||
pub struct ParsedModuleRef {
|
||||
module_ref: Arc<Parsed<ModModule>>,
|
||||
module: ParsedModule,
|
||||
indexed: Arc<indexed::IndexedModule>,
|
||||
}
|
||||
|
||||
impl ParsedModuleRef {
|
||||
pub fn as_arc(&self) -> &Arc<Parsed<ModModule>> {
|
||||
&self.module_ref
|
||||
/// Returns a reference to the [`ParsedModule`] that this instance was loaded from.
|
||||
pub fn module(&self) -> &ParsedModule {
|
||||
&self.module
|
||||
}
|
||||
|
||||
pub fn into_arc(self) -> Arc<Parsed<ModModule>> {
|
||||
self.module_ref
|
||||
/// Returns a reference to the AST node at the given index.
|
||||
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
|
||||
self.indexed.get_by_index(index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +138,247 @@ impl std::ops::Deref for ParsedModuleRef {
|
||||
type Target = Parsed<ModModule>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.module_ref
|
||||
&self.indexed.parsed
|
||||
}
|
||||
}
|
||||
|
||||
mod indexed {
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruff_python_ast::visitor::source_order::*;
|
||||
use ruff_python_ast::*;
|
||||
use ruff_python_parser::Parsed;
|
||||
|
||||
/// A wrapper around the AST that allows access to AST nodes by index.
|
||||
#[derive(Debug)]
|
||||
pub struct IndexedModule {
|
||||
index: Box<[AnyRootNodeRef<'static>]>,
|
||||
pub parsed: Parsed<ModModule>,
|
||||
}
|
||||
|
||||
impl IndexedModule {
|
||||
/// Create a new [`IndexedModule`] from the given AST.
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub fn new(parsed: Parsed<ModModule>) -> Arc<Self> {
|
||||
let mut visitor = Visitor {
|
||||
nodes: Vec::new(),
|
||||
index: 0,
|
||||
};
|
||||
|
||||
let mut inner = Arc::new(IndexedModule {
|
||||
parsed,
|
||||
index: Box::new([]),
|
||||
});
|
||||
|
||||
AnyNodeRef::from(inner.parsed.syntax()).visit_source_order(&mut visitor);
|
||||
|
||||
let index: Box<[AnyRootNodeRef<'_>]> = visitor.nodes.into_boxed_slice();
|
||||
|
||||
// SAFETY: We cast from `Box<[AnyRootNodeRef<'_>]>` to `Box<[AnyRootNodeRef<'static>]>`,
|
||||
// faking the 'static lifetime to create the self-referential struct. The node references
|
||||
// are into the `Arc<Parsed<ModModule>>`, so are valid for as long as the `IndexedModule`
|
||||
// is alive. We make sure to restore the correct lifetime in `get_by_index`.
|
||||
//
|
||||
// Note that we can never move the data within the `Arc` after this point.
|
||||
Arc::get_mut(&mut inner).unwrap().index =
|
||||
unsafe { Box::from_raw(Box::into_raw(index) as *mut [AnyRootNodeRef<'static>]) };
|
||||
|
||||
inner
|
||||
}
|
||||
|
||||
/// Returns the node at the given index.
|
||||
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
|
||||
// Note that this method restores the correct lifetime: the nodes are valid for as
|
||||
// long as the reference to `IndexedModule` is alive.
|
||||
self.index[index.as_usize()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A visitor that collects nodes in source order.
|
||||
pub struct Visitor<'a> {
|
||||
pub index: u32,
|
||||
pub nodes: Vec<AnyRootNodeRef<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> {
|
||||
fn visit_node<T>(&mut self, node: &'a T)
|
||||
where
|
||||
T: HasNodeIndex + std::fmt::Debug,
|
||||
AnyRootNodeRef<'a>: From<&'a T>,
|
||||
{
|
||||
node.node_index().set(self.index);
|
||||
self.nodes.push(AnyRootNodeRef::from(node));
|
||||
self.index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SourceOrderVisitor<'a> for Visitor<'a> {
|
||||
#[inline]
|
||||
fn visit_mod(&mut self, module: &'a Mod) {
|
||||
self.visit_node(module);
|
||||
walk_module(self, module);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
self.visit_node(stmt);
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_annotation(&mut self, expr: &'a Expr) {
|
||||
self.visit_node(expr);
|
||||
walk_annotation(self, expr);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
self.visit_node(expr);
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_decorator(&mut self, decorator: &'a Decorator) {
|
||||
self.visit_node(decorator);
|
||||
walk_decorator(self, decorator);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_comprehension(&mut self, comprehension: &'a Comprehension) {
|
||||
self.visit_node(comprehension);
|
||||
walk_comprehension(self, comprehension);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_except_handler(&mut self, except_handler: &'a ExceptHandler) {
|
||||
self.visit_node(except_handler);
|
||||
walk_except_handler(self, except_handler);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_arguments(&mut self, arguments: &'a Arguments) {
|
||||
self.visit_node(arguments);
|
||||
walk_arguments(self, arguments);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_parameters(&mut self, parameters: &'a Parameters) {
|
||||
self.visit_node(parameters);
|
||||
walk_parameters(self, parameters);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_parameter(&mut self, arg: &'a Parameter) {
|
||||
self.visit_node(arg);
|
||||
walk_parameter(self, arg);
|
||||
}
|
||||
|
||||
fn visit_parameter_with_default(
|
||||
&mut self,
|
||||
parameter_with_default: &'a ParameterWithDefault,
|
||||
) {
|
||||
self.visit_node(parameter_with_default);
|
||||
walk_parameter_with_default(self, parameter_with_default);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_keyword(&mut self, keyword: &'a Keyword) {
|
||||
self.visit_node(keyword);
|
||||
walk_keyword(self, keyword);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_alias(&mut self, alias: &'a Alias) {
|
||||
self.visit_node(alias);
|
||||
walk_alias(self, alias);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_with_item(&mut self, with_item: &'a WithItem) {
|
||||
self.visit_node(with_item);
|
||||
walk_with_item(self, with_item);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_type_params(&mut self, type_params: &'a TypeParams) {
|
||||
self.visit_node(type_params);
|
||||
walk_type_params(self, type_params);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_type_param(&mut self, type_param: &'a TypeParam) {
|
||||
self.visit_node(type_param);
|
||||
walk_type_param(self, type_param);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
|
||||
self.visit_node(match_case);
|
||||
walk_match_case(self, match_case);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_pattern(&mut self, pattern: &'a Pattern) {
|
||||
self.visit_node(pattern);
|
||||
walk_pattern(self, pattern);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_pattern_arguments(&mut self, pattern_arguments: &'a PatternArguments) {
|
||||
self.visit_node(pattern_arguments);
|
||||
walk_pattern_arguments(self, pattern_arguments);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_pattern_keyword(&mut self, pattern_keyword: &'a PatternKeyword) {
|
||||
self.visit_node(pattern_keyword);
|
||||
walk_pattern_keyword(self, pattern_keyword);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
|
||||
self.visit_node(elif_else_clause);
|
||||
walk_elif_else_clause(self, elif_else_clause);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_f_string(&mut self, f_string: &'a FString) {
|
||||
self.visit_node(f_string);
|
||||
walk_f_string(self, f_string);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_interpolated_string_element(
|
||||
&mut self,
|
||||
interpolated_string_element: &'a InterpolatedStringElement,
|
||||
) {
|
||||
self.visit_node(interpolated_string_element);
|
||||
walk_interpolated_string_element(self, interpolated_string_element);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_t_string(&mut self, t_string: &'a TString) {
|
||||
self.visit_node(t_string);
|
||||
walk_t_string(self, t_string);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) {
|
||||
self.visit_node(string_literal);
|
||||
walk_string_literal(self, string_literal);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_bytes_literal(&mut self, bytes_literal: &'a BytesLiteral) {
|
||||
self.visit_node(bytes_literal);
|
||||
walk_bytes_literal(self, bytes_literal);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_identifier(&mut self, identifier: &'a Identifier) {
|
||||
self.visit_node(identifier);
|
||||
walk_identifier(self, identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,30 @@ impl SystemPath {
|
||||
SystemPath::from_std_path(dunce::simplified(self.as_std_path())).unwrap()
|
||||
}
|
||||
|
||||
/// Returns `true` if the `SystemPath` is absolute, i.e., if it is independent of
|
||||
/// the current directory.
|
||||
///
|
||||
/// * On Unix, a path is absolute if it starts with the root, so
|
||||
/// `is_absolute` and [`has_root`] are equivalent.
|
||||
///
|
||||
/// * On Windows, a path is absolute if it has a prefix and starts with the
|
||||
/// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::system::SystemPath;
|
||||
///
|
||||
/// assert!(!SystemPath::new("foo.txt").is_absolute());
|
||||
/// ```
|
||||
///
|
||||
/// [`has_root`]: Utf8Path::has_root
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_absolute(&self) -> bool {
|
||||
self.0.is_absolute()
|
||||
}
|
||||
|
||||
/// Extracts the file extension, if possible.
|
||||
///
|
||||
/// The extension is:
|
||||
@@ -538,6 +562,10 @@ impl SystemPathBuf {
|
||||
self.0.into_std_path_buf()
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_path(&self) -> &SystemPath {
|
||||
SystemPath::new(&self.0)
|
||||
|
||||
@@ -39,6 +39,7 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
|
||||
module,
|
||||
level,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
let module = module.as_deref();
|
||||
let level = *level;
|
||||
@@ -78,7 +79,11 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
Stmt::Import(ast::StmtImport {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
for alias in names {
|
||||
if let Some(module_name) = ModuleName::new(alias.name.as_str()) {
|
||||
self.imports.push(CollectedImport::Import(module_name));
|
||||
@@ -122,7 +127,12 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
|
||||
|
||||
fn visit_expr(&mut self, expr: &'ast Expr) {
|
||||
if self.string_imports {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) = expr {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = expr
|
||||
{
|
||||
// Determine whether the string literal "looks like" an import statement: contains
|
||||
// a dot, and consists solely of valid Python identifiers.
|
||||
let value = value.to_str();
|
||||
|
||||
@@ -92,7 +92,7 @@ impl Db for ModuleDb {
|
||||
!file.path(self).is_vendored_path()
|
||||
}
|
||||
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
fn rule_selection(&self, _file: File) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.13"
|
||||
version = "0.12.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -179,3 +179,17 @@ def func():
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
return
|
||||
|
||||
# should not error - direct return with mutation (Issue #18399)
|
||||
def fail_map(mapping):
|
||||
for key in mapping:
|
||||
return mapping.pop(key)
|
||||
|
||||
def success_map(mapping):
|
||||
for key in mapping:
|
||||
ret = mapping.pop(key) # should not error
|
||||
return ret
|
||||
|
||||
def fail_list(seq):
|
||||
for val in seq:
|
||||
return seq.pop(4)
|
||||
|
||||
@@ -181,3 +181,34 @@ MetaType = TypeVar("MetaType")
|
||||
class MetaTestClass(type):
|
||||
def m(cls: MetaType) -> MetaType:
|
||||
return cls
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
class BadClassWithStringTypeHints:
|
||||
def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019
|
||||
|
||||
|
||||
class BadSubscriptReturnTypeWithStringTypeHints:
|
||||
@classmethod
|
||||
def m[S](cls: "type[S]") -> "type[S]": ... # PYI019
|
||||
|
||||
|
||||
class GoodClassWiStringTypeHints:
|
||||
@classmethod
|
||||
def good_cls_method_with_mixed_annotations(cls: "type[Self]", arg: str) -> Self: ...
|
||||
@staticmethod
|
||||
def good_static_method_with_string_annotations(arg: "_S") -> "_S": ...
|
||||
@classmethod
|
||||
def good_class_method_with_args_string_annotations(cls, arg1: "_S", arg2: "_S") -> "_S": ...
|
||||
|
||||
@@ -172,3 +172,36 @@ MetaType = TypeVar("MetaType")
|
||||
class MetaTestClass(type):
|
||||
def m(cls: MetaType) -> MetaType:
|
||||
return cls
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class BadClassWithStringTypeHints:
|
||||
def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019
|
||||
|
||||
|
||||
class BadSubscriptReturnTypeWithStringTypeHints:
|
||||
@classmethod
|
||||
def m[S](cls: "type[S]") -> "type[S]": ... # PYI019
|
||||
|
||||
|
||||
class GoodClassWithStringTypeHints:
|
||||
@classmethod
|
||||
def good_cls_method_with_mixed_annotations(cls: "type[Self]", arg: str) -> Self: ...
|
||||
@staticmethod
|
||||
def good_static_method_with_string_annotations(arg: "_S") -> "_S": ...
|
||||
@classmethod
|
||||
def good_class_method_with_args_string_annotations(cls, arg1: "_S", arg2: "_S") -> "_S": ...
|
||||
|
||||
|
||||
@@ -52,3 +52,15 @@ class MyList(Sized, Generic[T]): # Generic already in last place
|
||||
|
||||
class SomeGeneric(Generic[T]): # Only one generic
|
||||
pass
|
||||
|
||||
|
||||
# syntax errors with starred and keyword arguments from
|
||||
# https://github.com/astral-sh/ruff/issues/18602
|
||||
class C1(Generic[T], str, **{"metaclass": type}): # PYI059
|
||||
...
|
||||
|
||||
class C2(Generic[T], str, metaclass=type): # PYI059
|
||||
...
|
||||
|
||||
class C3(Generic[T], metaclass=type, *[str]): # PYI059 but no fix
|
||||
...
|
||||
|
||||
@@ -421,3 +421,14 @@ def func(a: dict[str, int]) -> list[dict[str, int]]:
|
||||
if "services" in a:
|
||||
services = a["services"]
|
||||
return services
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/18411
|
||||
def f():
|
||||
(#=
|
||||
x) = 1
|
||||
return x
|
||||
|
||||
def f():
|
||||
x = (1
|
||||
)
|
||||
return x
|
||||
|
||||
@@ -81,3 +81,9 @@ foo = {}
|
||||
class Bar(type(foo)):
|
||||
def foo_method(self):
|
||||
pass
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18459
|
||||
class Example:
|
||||
@classmethod
|
||||
def function(this):
|
||||
cls = 1234
|
||||
|
||||
@@ -134,3 +134,9 @@ class MyMeta(type):
|
||||
|
||||
class MyProtocolMeta(type(Protocol)):
|
||||
def __subclasscheck__(cls, other): ...
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18459
|
||||
class C:
|
||||
def f(this):
|
||||
self = 123
|
||||
|
||||
@@ -176,4 +176,17 @@ x = lambda: (
|
||||
x = lambda: (
|
||||
# comment
|
||||
y := 10
|
||||
)
|
||||
)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18475
|
||||
foo_tooltip = (
|
||||
lambda x, data: f"\nfoo: {data['foo'][int(x)]}"
|
||||
if data["foo"] is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
foo_tooltip = (
|
||||
lambda x, data: f"\nfoo: {data['foo'][int(x)]}" +
|
||||
more
|
||||
|
||||
)
|
||||
|
||||
@@ -2,5 +2,8 @@ FIRST, FIRST = (1, 2) # PLW0128
|
||||
FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
|
||||
FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
|
||||
FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
|
||||
FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128
|
||||
FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128
|
||||
FIRST, *FIRST = (1, 2) # PLW0128
|
||||
|
||||
FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK
|
||||
|
||||
@@ -79,3 +79,29 @@ class DataClass:
|
||||
def normal(self):
|
||||
super(DataClass, self).f() # OK
|
||||
super().f() # OK (`TypeError` in practice)
|
||||
|
||||
|
||||
# see: https://github.com/astral-sh/ruff/issues/18477
|
||||
class A:
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
|
||||
class B(A):
|
||||
def bar(self):
|
||||
super(__class__, self).foo()
|
||||
|
||||
|
||||
# see: https://github.com/astral-sh/ruff/issues/18684
|
||||
class C:
|
||||
def f(self):
|
||||
super = print
|
||||
super(C, self)
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
|
||||
class C:
|
||||
def f(self):
|
||||
builtins.super(C, self)
|
||||
|
||||
@@ -42,3 +42,8 @@ class ServiceRefOrValue:
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7201
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[str]is not True = None
|
||||
|
||||
|
||||
# Test for: https://github.com/astral-sh/ruff/issues/18508
|
||||
# Optional[None] should not be offered a fix
|
||||
foo: Optional[None] = None
|
||||
|
||||
@@ -174,3 +174,43 @@ def _():
|
||||
global global_foo
|
||||
for [a, b, (global_foo, c)] in d:
|
||||
f.write((a, b))
|
||||
|
||||
|
||||
# Test cases for lambda and ternary expressions - https://github.com/astral-sh/ruff/issues/18590
|
||||
|
||||
def _():
|
||||
with Path("file.txt").open("w", encoding="utf-8") as f:
|
||||
for l in lambda: 0:
|
||||
f.write(f"[{l}]")
|
||||
|
||||
|
||||
def _():
|
||||
with Path("file.txt").open("w", encoding="utf-8") as f:
|
||||
for l in (1,) if True else (2,):
|
||||
f.write(f"[{l}]")
|
||||
|
||||
|
||||
# don't need to add parentheses when making a function argument
|
||||
def _():
|
||||
with open("file", "w") as f:
|
||||
for line in lambda: 0:
|
||||
f.write(line)
|
||||
|
||||
|
||||
def _():
|
||||
with open("file", "w") as f:
|
||||
for line in (1,) if True else (2,):
|
||||
f.write(line)
|
||||
|
||||
|
||||
# don't add extra parentheses if they're already parenthesized
|
||||
def _():
|
||||
with open("file", "w") as f:
|
||||
for line in (lambda: 0):
|
||||
f.write(f"{line}")
|
||||
|
||||
|
||||
def _():
|
||||
with open("file", "w") as f:
|
||||
for line in ((1,) if True else (2,)):
|
||||
f.write(f"{line}")
|
||||
|
||||
@@ -74,3 +74,28 @@ async def f(y):
|
||||
def g():
|
||||
for x in (set(),):
|
||||
x.add(x)
|
||||
|
||||
|
||||
# Test cases for lambda and ternary expressions - https://github.com/astral-sh/ruff/issues/18590
|
||||
|
||||
s = set()
|
||||
|
||||
for x in lambda: 0:
|
||||
s.discard(-x)
|
||||
|
||||
for x in (1,) if True else (2,):
|
||||
s.add(-x)
|
||||
|
||||
# don't add extra parens
|
||||
for x in (lambda: 0):
|
||||
s.discard(-x)
|
||||
|
||||
for x in ((1,) if True else (2,)):
|
||||
s.add(-x)
|
||||
|
||||
# don't add parens directly in function call
|
||||
for x in lambda: 0:
|
||||
s.discard(x)
|
||||
|
||||
for x in (1,) if True else (2,):
|
||||
s.add(x)
|
||||
|
||||
@@ -43,3 +43,33 @@ log(1, math.e)
|
||||
|
||||
math.log(1, 2.0001)
|
||||
math.log(1, 10.0001)
|
||||
|
||||
|
||||
# see: https://github.com/astral-sh/ruff/issues/18639
|
||||
math.log(1, 10 # comment
|
||||
)
|
||||
|
||||
math.log(1,
|
||||
10 # comment
|
||||
)
|
||||
|
||||
math.log(1 # comment
|
||||
, # comment
|
||||
10 # comment
|
||||
)
|
||||
|
||||
math.log(
|
||||
1 # comment
|
||||
,
|
||||
10 # comment
|
||||
)
|
||||
|
||||
math.log(4.13e223, 2)
|
||||
math.log(4.14e223, 10)
|
||||
|
||||
|
||||
def print_log(*args):
|
||||
try:
|
||||
print(math.log(*args, math.e))
|
||||
except TypeError as e:
|
||||
print(repr(e))
|
||||
|
||||
@@ -56,3 +56,38 @@ def f():
|
||||
|
||||
def f():
|
||||
queue = deque() # Ok
|
||||
|
||||
def f():
|
||||
x = 0 or(deque)([])
|
||||
|
||||
|
||||
# regression tests for https://github.com/astral-sh/ruff/issues/18612
|
||||
def f():
|
||||
deque([], *[10]) # RUF037 but no fix
|
||||
deque([], **{"maxlen": 10}) # RUF037
|
||||
deque([], foo=1) # RUF037
|
||||
|
||||
|
||||
# Somewhat related to the issue, both okay because we can't generally look
|
||||
# inside *args or **kwargs
|
||||
def f():
|
||||
deque(*([], 10)) # Ok
|
||||
deque(**{"iterable": [], "maxlen": 10}) # Ok
|
||||
|
||||
# The fix was actually always unsafe in the presence of comments. all of these
|
||||
# are deleted
|
||||
def f():
|
||||
deque( # a comment in deque, deleted
|
||||
[ # a comment _in_ the list, deleted
|
||||
], # a comment after the list, deleted
|
||||
maxlen=10, # a comment on maxlen, deleted
|
||||
) # only this is preserved
|
||||
|
||||
|
||||
# `maxlen` can also be passed positionally
|
||||
def f():
|
||||
deque([], 10)
|
||||
|
||||
|
||||
def f():
|
||||
deque([], iterable=[])
|
||||
|
||||
@@ -149,23 +149,39 @@ value = not my_dict.get("key", 0) # [RUF056]
|
||||
value = not my_dict.get("key", 0.0) # [RUF056]
|
||||
value = not my_dict.get("key", "") # [RUF056]
|
||||
|
||||
# testing dict.get call using kwargs
|
||||
value = not my_dict.get(key="key", default=False) # [RUF056]
|
||||
value = not my_dict.get(default=[], key="key") # [RUF056]
|
||||
|
||||
# testing invalid dict.get call with inline comment
|
||||
value = not my_dict.get("key", # comment1
|
||||
[] # comment2
|
||||
) # [RUF056]
|
||||
|
||||
# testing invalid dict.get call with kwargs and inline comment
|
||||
value = not my_dict.get(key="key", # comment1
|
||||
default=False # comment2
|
||||
) # [RUF056]
|
||||
value = not my_dict.get(default=[], # comment1
|
||||
key="key" # comment2
|
||||
) # [RUF056]
|
||||
# regression tests for https://github.com/astral-sh/ruff/issues/18628
|
||||
# we should avoid fixes when there are "unknown" arguments present, including
|
||||
# extra positional arguments, either of the positional-only arguments passed as
|
||||
# a keyword, or completely unknown keywords.
|
||||
|
||||
# testing invalid dict.get calls
|
||||
value = not my_dict.get(key="key", other="something", default=False)
|
||||
value = not my_dict.get(default=False, other="something", key="test")
|
||||
# extra positional
|
||||
not my_dict.get("key", False, "?!")
|
||||
|
||||
# `default` is positional-only, so these are invalid
|
||||
not my_dict.get("key", default=False)
|
||||
not my_dict.get(key="key", default=False)
|
||||
not my_dict.get(default=[], key="key")
|
||||
not my_dict.get(default=False)
|
||||
not my_dict.get(key="key", other="something", default=False)
|
||||
not my_dict.get(default=False, other="something", key="test")
|
||||
|
||||
# comments don't really matter here because of the kwargs but include them for
|
||||
# completeness
|
||||
not my_dict.get(
|
||||
key="key", # comment1
|
||||
default=False, # comment2
|
||||
) # comment 3
|
||||
not my_dict.get(
|
||||
default=[], # comment1
|
||||
key="key", # comment2
|
||||
) # comment 3
|
||||
|
||||
# the fix is arguably okay here because the same `takes no keyword arguments`
|
||||
# TypeError is raised at runtime before and after the fix, but we still bail
|
||||
# out for having an unrecognized number of arguments
|
||||
not my_dict.get("key", False, foo=...)
|
||||
|
||||
@@ -94,3 +94,9 @@ def f():
|
||||
(exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2)
|
||||
}
|
||||
return hash_map
|
||||
|
||||
|
||||
# see: https://github.com/astral-sh/ruff/issues/18507
|
||||
def f(_x):
|
||||
x, = "1"
|
||||
print(_x)
|
||||
|
||||
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_deprecation_warning(s):
|
||||
warnings.warn(s, DeprecationWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.deprecated_call():
|
||||
raise_deprecation_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning))
|
||||
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def func(a, b):
|
||||
return a / b
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_ok_as():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_match():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_kwargs():
|
||||
pytest.raises(func=func, expected_exception=ZeroDivisionError)
|
||||
|
||||
|
||||
def test_error_multi_statement():
|
||||
excinfo = pytest.raises(ValueError, int, "hello")
|
||||
assert excinfo.match("^invalid literal")
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.raises(ZeroDivisionError, lambda: 1 / 0)
|
||||
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_user_warning(s):
|
||||
warnings.warn(s, UserWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.warns(UserWarning):
|
||||
raise_user_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning))
|
||||
@@ -15,6 +15,7 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker)
|
||||
name,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::BareExcept) {
|
||||
pycodestyle::rules::bare_except(checker, type_.as_deref(), body, except_handler);
|
||||
|
||||
@@ -185,12 +185,14 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
parenthesized: _,
|
||||
})
|
||||
| Expr::List(ast::ExprList {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if ctx.is_store() {
|
||||
let check_too_many_expressions = checker.enabled(Rule::ExpressionsInStarAssignment);
|
||||
@@ -205,7 +207,12 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
Expr::Name(ast::ExprName { id, ctx, range }) => {
|
||||
Expr::Name(ast::ExprName {
|
||||
id,
|
||||
ctx,
|
||||
range,
|
||||
node_index: _,
|
||||
}) => {
|
||||
match ctx {
|
||||
ExprContext::Load => {
|
||||
if checker.enabled(Rule::TypingTextStrAlias) {
|
||||
@@ -472,8 +479,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -966,6 +975,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
]) {
|
||||
flake8_pytest_style::rules::raises_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::LegacyFormPytestRaises) {
|
||||
ruff::rules::legacy_raises_warns_deprecated_call(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[Rule::PytestWarnsWithoutWarning, Rule::PytestWarnsTooBroad]) {
|
||||
flake8_pytest_style::rules::warns_call(checker, call);
|
||||
}
|
||||
@@ -1261,6 +1273,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
op: Operator::Mod,
|
||||
right,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if let Expr::StringLiteral(format_string @ ast::ExprStringLiteral { value, .. }) =
|
||||
@@ -1426,6 +1439,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
op,
|
||||
operand,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) {
|
||||
@@ -1452,6 +1466,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
ops,
|
||||
comparators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
|
||||
@@ -1530,7 +1545,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
refurb::rules::math_constant(checker, number_literal);
|
||||
}
|
||||
}
|
||||
Expr::StringLiteral(string_like @ ast::ExprStringLiteral { value, range: _ }) => {
|
||||
Expr::StringLiteral(
|
||||
string_like @ ast::ExprStringLiteral {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
for string_part in value {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, string_part);
|
||||
@@ -1551,6 +1572,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
body,
|
||||
orelse,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::IfElseBlockInsteadOfDictGet) {
|
||||
@@ -1585,6 +1607,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
@@ -1615,6 +1638,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
@@ -1646,6 +1670,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
value,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
@@ -1684,6 +1709,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
parenthesized: _,
|
||||
},
|
||||
) => {
|
||||
|
||||
@@ -18,7 +18,11 @@ use ruff_python_ast::PythonVersion;
|
||||
/// Run lint rules over a [`Stmt`] syntax node.
|
||||
pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
match stmt {
|
||||
Stmt::Global(ast::StmtGlobal { names, range: _ }) => {
|
||||
Stmt::Global(ast::StmtGlobal {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::GlobalAtModuleLevel) {
|
||||
pylint::rules::global_at_module_level(checker, stmt);
|
||||
}
|
||||
@@ -28,7 +32,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Nonlocal(nonlocal @ ast::StmtNonlocal { names, range: _ }) => {
|
||||
Stmt::Nonlocal(
|
||||
nonlocal @ ast::StmtNonlocal {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||
for name in names {
|
||||
pycodestyle::rules::ambiguous_variable_name(checker, name, name.range());
|
||||
@@ -80,6 +90,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
body,
|
||||
type_params: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
@@ -381,6 +392,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
decorator_list,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::NoClassmethodDecorator) {
|
||||
@@ -542,7 +554,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
ruff::rules::implicit_class_var_in_dataclass(checker, class_def);
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
Stmt::Import(ast::StmtImport {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
||||
pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names);
|
||||
}
|
||||
@@ -699,6 +715,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
module,
|
||||
level,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
let level = *level;
|
||||
@@ -1142,6 +1159,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
test,
|
||||
msg,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if !checker.semantic.in_type_checking_block() {
|
||||
@@ -1243,6 +1261,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
orelse,
|
||||
is_async,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::TooManyNestedBlocks) {
|
||||
@@ -1606,7 +1625,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_pyi::rules::t_suffixed_type_alias(checker, name);
|
||||
}
|
||||
}
|
||||
Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => {
|
||||
Stmt::Delete(
|
||||
delete @ ast::StmtDelete {
|
||||
targets,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for target in targets {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
@@ -1618,7 +1643,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
refurb::rules::delete_full_slice(checker, delete);
|
||||
}
|
||||
}
|
||||
Stmt::Expr(expr @ ast::StmtExpr { value, range: _ }) => {
|
||||
Stmt::Expr(
|
||||
expr @ ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UselessComparison) {
|
||||
flake8_bugbear::rules::useless_comparison(checker, value);
|
||||
}
|
||||
@@ -1645,6 +1676,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
subject: _,
|
||||
cases,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::NanComparison) {
|
||||
pylint::rules::nan_comparison_match(checker, cases);
|
||||
|
||||
@@ -65,7 +65,7 @@ use crate::docstrings::extraction::ExtractionTarget;
|
||||
use crate::importer::{ImportRequest, Importer, ResolutionError};
|
||||
use crate::noqa::NoqaMapping;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled};
|
||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pyflakes::rules::{
|
||||
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
|
||||
@@ -663,9 +663,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_)
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
if is_semantic_errors_enabled(self.settings) {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,10 +834,15 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
op: _,
|
||||
value: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.handle_node_load(target);
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
Stmt::Import(ast::StmtImport {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if self.semantic.at_top_level() {
|
||||
self.importer.visit_import(stmt);
|
||||
}
|
||||
@@ -893,6 +896,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
module,
|
||||
level,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if self.semantic.at_top_level() {
|
||||
self.importer.visit_import(stmt);
|
||||
@@ -954,7 +958,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Global(ast::StmtGlobal { names, range: _ }) => {
|
||||
Stmt::Global(ast::StmtGlobal {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if !self.semantic.scope_id.is_global() {
|
||||
for name in names {
|
||||
let binding_id = self.semantic.global_scope().get(name);
|
||||
@@ -976,7 +984,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => {
|
||||
Stmt::Nonlocal(ast::StmtNonlocal {
|
||||
names,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if !self.semantic.scope_id.is_global() {
|
||||
for name in names {
|
||||
if let Some((scope_id, binding_id)) = self.semantic.nonlocal(name) {
|
||||
@@ -1186,6 +1198,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
range: _,
|
||||
node_index: _,
|
||||
name,
|
||||
type_params,
|
||||
value,
|
||||
@@ -1280,6 +1293,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
test,
|
||||
msg,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
let snapshot = self.semantic.flags;
|
||||
self.semantic.flags |= SemanticModelFlags::ASSERT_STATEMENT;
|
||||
@@ -1294,6 +1308,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
body,
|
||||
is_async: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
for item in items {
|
||||
self.visit_with_item(item);
|
||||
@@ -1307,6 +1322,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
body,
|
||||
orelse,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_boolean_test(test);
|
||||
self.visit_body(body);
|
||||
@@ -1318,6 +1334,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
self.visit_boolean_test(test);
|
||||
@@ -1437,15 +1454,27 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
func,
|
||||
arguments: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||
if let Expr::Name(ast::ExprName {
|
||||
id,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = func.as_ref()
|
||||
{
|
||||
if id == "locals" && ctx.is_load() {
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
scope.set_uses_locals();
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Name(ast::ExprName { id, ctx, range: _ }) => match ctx {
|
||||
Expr::Name(ast::ExprName {
|
||||
id,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => match ctx {
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Store => self.handle_node_store(id, expr),
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
@@ -1460,6 +1489,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_generators(GeneratorKind::ListComprehension, generators);
|
||||
self.visit_expr(elt);
|
||||
@@ -1468,6 +1498,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_generators(GeneratorKind::SetComprehension, generators);
|
||||
self.visit_expr(elt);
|
||||
@@ -1476,6 +1507,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
self.visit_generators(GeneratorKind::Generator, generators);
|
||||
@@ -1486,6 +1518,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
value,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_generators(GeneratorKind::DictComprehension, generators);
|
||||
self.visit_expr(key);
|
||||
@@ -1496,6 +1529,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
parameters,
|
||||
body: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
) => {
|
||||
// Visit the default arguments, but avoid the body, which will be deferred.
|
||||
@@ -1517,6 +1551,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
body,
|
||||
orelse,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_boolean_test(test);
|
||||
self.visit_expr(body);
|
||||
@@ -1526,6 +1561,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
op: UnaryOp::Not,
|
||||
operand,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_boolean_test(operand);
|
||||
}
|
||||
@@ -1533,6 +1569,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
func,
|
||||
arguments,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_expr(func);
|
||||
|
||||
@@ -1647,6 +1684,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
arg,
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = keyword;
|
||||
if let Some(id) = arg {
|
||||
if matches!(&**id, "bound" | "default") {
|
||||
@@ -1738,7 +1776,12 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
for arg in args {
|
||||
if let Expr::Dict(ast::ExprDict { items, range: _ }) = arg {
|
||||
if let Expr::Dict(ast::ExprDict {
|
||||
items,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = arg
|
||||
{
|
||||
for ast::DictItem { key, value } in items {
|
||||
if let Some(key) = key {
|
||||
self.visit_non_type_definition(key);
|
||||
@@ -1776,6 +1819,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
value,
|
||||
arg,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = keyword;
|
||||
if arg.as_ref().is_some_and(|arg| arg == "type") {
|
||||
self.visit_type_definition(value);
|
||||
@@ -1804,6 +1848,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
slice,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
// Only allow annotations in `ExprContext::Load`. If we have, e.g.,
|
||||
// `obj["foo"]["bar"]`, we need to avoid treating the `obj["foo"]`
|
||||
@@ -1843,6 +1888,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
node_index: _,
|
||||
parenthesized: _,
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
@@ -1867,7 +1913,12 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
}
|
||||
Some(typing::SubscriptKind::TypedDict) => {
|
||||
if let Expr::Dict(ast::ExprDict { items, range: _ }) = slice.as_ref() {
|
||||
if let Expr::Dict(ast::ExprDict {
|
||||
items,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
for item in items {
|
||||
if let Some(key) = &item.key {
|
||||
self.visit_non_type_definition(key);
|
||||
@@ -1906,6 +1957,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
target,
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.visit_expr(value);
|
||||
|
||||
@@ -1955,6 +2007,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
name,
|
||||
body: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Some(name) = name {
|
||||
// Store the existing binding, if any.
|
||||
@@ -2029,6 +2082,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
| Pattern::MatchStar(ast::PatternMatchStar {
|
||||
name: Some(name),
|
||||
range: _,
|
||||
node_index: _,
|
||||
})
|
||||
| Pattern::MatchMapping(ast::PatternMatchMapping {
|
||||
rest: Some(name), ..
|
||||
@@ -2088,6 +2142,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
default,
|
||||
name: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Some(expr) = bound {
|
||||
self.visit
|
||||
@@ -2104,6 +2159,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
default,
|
||||
name: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Some(expr) = default {
|
||||
self.visit
|
||||
@@ -2115,6 +2171,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
default,
|
||||
name: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Some(expr) = default {
|
||||
self.visit
|
||||
@@ -2833,6 +2890,7 @@ impl<'a> Checker<'a> {
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
})) = self.semantic.current_expression()
|
||||
else {
|
||||
unreachable!("Expected Expr::Lambda");
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::fix::edits::delete_comment;
|
||||
use crate::noqa::{
|
||||
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
||||
};
|
||||
use crate::preview::is_check_file_level_directives_enabled;
|
||||
use crate::registry::{AsRule, Rule, RuleSet};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::pygrep_hooks;
|
||||
@@ -112,25 +111,16 @@ pub(crate) fn check_noqa(
|
||||
&& !exemption.includes(Rule::UnusedNOQA)
|
||||
&& !per_file_ignores.contains(Rule::UnusedNOQA)
|
||||
{
|
||||
let directives: Vec<_> = if is_check_file_level_directives_enabled(settings) {
|
||||
noqa_directives
|
||||
.lines()
|
||||
.iter()
|
||||
.map(|line| (&line.directive, &line.matches, false))
|
||||
.chain(
|
||||
file_noqa_directives
|
||||
.lines()
|
||||
.iter()
|
||||
.map(|line| (&line.parsed_file_exemption, &line.matches, true)),
|
||||
)
|
||||
.collect()
|
||||
} else {
|
||||
noqa_directives
|
||||
.lines()
|
||||
.iter()
|
||||
.map(|line| (&line.directive, &line.matches, false))
|
||||
.collect()
|
||||
};
|
||||
let directives = noqa_directives
|
||||
.lines()
|
||||
.iter()
|
||||
.map(|line| (&line.directive, &line.matches, false))
|
||||
.chain(
|
||||
file_noqa_directives
|
||||
.lines()
|
||||
.iter()
|
||||
.map(|line| (&line.parsed_file_exemption, &line.matches, true)),
|
||||
);
|
||||
for (directive, matches, is_file_level) in directives {
|
||||
match directive {
|
||||
Directive::All(directive) => {
|
||||
|
||||
@@ -201,7 +201,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0207") => (RuleGroup::Preview, rules::pylint::rules::MissingMaxsplitArg),
|
||||
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
|
||||
(Pylint, "C0415") => (RuleGroup::Stable, rules::pylint::rules::ImportOutsideTopLevel),
|
||||
(Pylint, "C1802") => (RuleGroup::Stable, rules::pylint::rules::LenTest),
|
||||
(Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName),
|
||||
@@ -270,7 +270,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1730") => (RuleGroup::Stable, rules::pylint::rules::IfStmtMinMax),
|
||||
(Pylint, "R1716") => (RuleGroup::Stable, rules::pylint::rules::BooleanChainedComparison),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1733") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R2044") => (RuleGroup::Stable, rules::pylint::rules::EmptyComment),
|
||||
@@ -281,7 +281,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Pylint, "W0101") => (RuleGroup::Preview, rules::pylint::rules::UnreachableCode),
|
||||
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
|
||||
(Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0177") => (RuleGroup::Stable, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
||||
(Pylint, "W0128") => (RuleGroup::Stable, rules::pylint::rules::RedeclaredAssignedName),
|
||||
@@ -303,7 +303,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
(Pylint, "W1514") => (RuleGroup::Preview, rules::pylint::rules::UnspecifiedEncoding),
|
||||
(Pylint, "W1641") => (RuleGroup::Preview, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W1641") => (RuleGroup::Stable, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Stable, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
|
||||
(Pylint, "W3201") => (RuleGroup::Preview, rules::pylint::rules::BadDunderMethodName),
|
||||
@@ -549,10 +549,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum),
|
||||
(Pyupgrade, "043") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryDefaultTypeArgs),
|
||||
(Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack),
|
||||
(Pyupgrade, "045") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional),
|
||||
(Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass),
|
||||
(Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction),
|
||||
(Pyupgrade, "049") => (RuleGroup::Preview, rules::pyupgrade::rules::PrivateTypeParameter),
|
||||
(Pyupgrade, "045") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604AnnotationOptional),
|
||||
(Pyupgrade, "046") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericClass),
|
||||
(Pyupgrade, "047") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericFunction),
|
||||
(Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter),
|
||||
(Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType),
|
||||
|
||||
// pydocstyle
|
||||
@@ -660,7 +660,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "317") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLSaxUsage),
|
||||
(Flake8Bandit, "318") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage),
|
||||
(Flake8Bandit, "319") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage),
|
||||
(Flake8Bandit, "320") => (RuleGroup::Deprecated, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage),
|
||||
(Flake8Bandit, "320") => (RuleGroup::Removed, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage),
|
||||
(Flake8Bandit, "321") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousFTPLibUsage),
|
||||
(Flake8Bandit, "323") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage),
|
||||
(Flake8Bandit, "324") => (RuleGroup::Stable, rules::flake8_bandit::rules::HashlibInsecureHashFunction),
|
||||
@@ -752,7 +752,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(PandasVet, "013") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfDotStack),
|
||||
(PandasVet, "015") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfPdMerge),
|
||||
(PandasVet, "101") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
|
||||
(PandasVet, "901") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
(PandasVet, "901") => (RuleGroup::Deprecated, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
|
||||
// flake8-errmsg
|
||||
(Flake8ErrMsg, "101") => (RuleGroup::Stable, rules::flake8_errmsg::rules::RawStringInException),
|
||||
@@ -846,10 +846,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "025") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture),
|
||||
(Flake8PytestStyle, "026") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters),
|
||||
(Flake8PytestStyle, "027") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion),
|
||||
(Flake8PytestStyle, "028") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument),
|
||||
(Flake8PytestStyle, "028") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument),
|
||||
(Flake8PytestStyle, "029") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithoutWarning),
|
||||
(Flake8PytestStyle, "030") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsTooBroad),
|
||||
(Flake8PytestStyle, "031") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements),
|
||||
(Flake8PytestStyle, "030") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestWarnsTooBroad),
|
||||
(Flake8PytestStyle, "031") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements),
|
||||
|
||||
// flake8-pie
|
||||
(Flake8Pie, "790") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryPlaceholder),
|
||||
@@ -997,7 +997,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "024") => (RuleGroup::Stable, rules::ruff::rules::MutableFromkeysValue),
|
||||
(Ruff, "026") => (RuleGroup::Stable, rules::ruff::rules::DefaultFactoryKwarg),
|
||||
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
|
||||
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
||||
(Ruff, "028") => (RuleGroup::Stable, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
||||
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
|
||||
(Ruff, "030") => (RuleGroup::Stable, rules::ruff::rules::AssertWithPrintMessage),
|
||||
(Ruff, "031") => (RuleGroup::Preview, rules::ruff::rules::IncorrectlyParenthesizedTupleInSubscript),
|
||||
@@ -1016,17 +1016,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "046") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryCastToInt),
|
||||
(Ruff, "047") => (RuleGroup::Preview, rules::ruff::rules::NeedlessElse),
|
||||
(Ruff, "048") => (RuleGroup::Stable, rules::ruff::rules::MapIntVersionParsing),
|
||||
(Ruff, "049") => (RuleGroup::Preview, rules::ruff::rules::DataclassEnum),
|
||||
(Ruff, "049") => (RuleGroup::Stable, rules::ruff::rules::DataclassEnum),
|
||||
(Ruff, "051") => (RuleGroup::Stable, rules::ruff::rules::IfKeyInDictDel),
|
||||
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
|
||||
(Ruff, "053") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars),
|
||||
(Ruff, "053") => (RuleGroup::Stable, rules::ruff::rules::ClassWithMixedTypeVars),
|
||||
(Ruff, "054") => (RuleGroup::Preview, rules::ruff::rules::IndentedFormFeed),
|
||||
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
|
||||
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
|
||||
(Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound),
|
||||
(Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip),
|
||||
(Ruff, "057") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryRound),
|
||||
(Ruff, "058") => (RuleGroup::Stable, rules::ruff::rules::StarmapZip),
|
||||
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
|
||||
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),
|
||||
@@ -1115,10 +1116,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "116") => (RuleGroup::Preview, rules::refurb::rules::FStringNumberFormat),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
(Refurb, "122") => (RuleGroup::Preview, rules::refurb::rules::ForLoopWrites),
|
||||
(Refurb, "122") => (RuleGroup::Stable, rules::refurb::rules::ForLoopWrites),
|
||||
(Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor),
|
||||
(Refurb, "131") => (RuleGroup::Preview, rules::refurb::rules::DeleteFullSlice),
|
||||
(Refurb, "132") => (RuleGroup::Preview, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "132") => (RuleGroup::Stable, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "136") => (RuleGroup::Stable, rules::refurb::rules::IfExprMinMax),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "142") => (RuleGroup::Preview, rules::refurb::rules::ForLoopSetMutations),
|
||||
@@ -1127,12 +1128,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant),
|
||||
(Refurb, "154") => (RuleGroup::Preview, rules::refurb::rules::RepeatedGlobal),
|
||||
(Refurb, "156") => (RuleGroup::Preview, rules::refurb::rules::HardcodedStringCharset),
|
||||
(Refurb, "157") => (RuleGroup::Preview, rules::refurb::rules::VerboseDecimalConstructor),
|
||||
(Refurb, "157") => (RuleGroup::Stable, rules::refurb::rules::VerboseDecimalConstructor),
|
||||
(Refurb, "161") => (RuleGroup::Stable, rules::refurb::rules::BitCount),
|
||||
(Refurb, "162") => (RuleGroup::Preview, rules::refurb::rules::FromisoformatReplaceZ),
|
||||
(Refurb, "162") => (RuleGroup::Stable, rules::refurb::rules::FromisoformatReplaceZ),
|
||||
(Refurb, "163") => (RuleGroup::Stable, rules::refurb::rules::RedundantLogBase),
|
||||
(Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat),
|
||||
(Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr),
|
||||
(Refurb, "166") => (RuleGroup::Stable, rules::refurb::rules::IntOnSlicedStr),
|
||||
(Refurb, "167") => (RuleGroup::Stable, rules::refurb::rules::RegexFlagAlias),
|
||||
(Refurb, "168") => (RuleGroup::Stable, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "169") => (RuleGroup::Stable, rules::refurb::rules::TypeNoneComparison),
|
||||
@@ -1151,7 +1152,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Logging, "004") => (RuleGroup::Preview, rules::flake8_logging::rules::LogExceptionOutsideExceptHandler),
|
||||
(Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
|
||||
(Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn),
|
||||
(Flake8Logging, "014") => (RuleGroup::Preview, rules::flake8_logging::rules::ExcInfoOutsideExceptHandler),
|
||||
(Flake8Logging, "014") => (RuleGroup::Stable, rules::flake8_logging::rules::ExcInfoOutsideExceptHandler),
|
||||
(Flake8Logging, "015") => (RuleGroup::Stable, rules::flake8_logging::rules::RootLoggerCall),
|
||||
|
||||
_ => return None,
|
||||
|
||||
@@ -73,6 +73,7 @@ impl StatementVisitor<'_> for StringLinesVisitor<'_> {
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value: expr,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = stmt
|
||||
{
|
||||
if expr.is_string_literal_expr() {
|
||||
|
||||
@@ -7,7 +7,12 @@ use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, Member
|
||||
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&ast::ExprStringLiteral> {
|
||||
let stmt = suite.first()?;
|
||||
// Require the docstring to be a standalone expression.
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
let Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = stmt
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
// Only match strings.
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
|
||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::textwrap::dedent_to;
|
||||
@@ -257,19 +257,23 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
}
|
||||
|
||||
/// Generic function to add arguments or keyword arguments to function calls.
|
||||
///
|
||||
/// The new argument will be inserted before the first existing keyword argument in `arguments`, if
|
||||
/// there are any present. Otherwise, the new argument is added to the end of the argument list.
|
||||
pub(crate) fn add_argument(
|
||||
argument: &str,
|
||||
arguments: &Arguments,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Edit {
|
||||
if let Some(last) = arguments.arguments_source_order().last() {
|
||||
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
|
||||
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
|
||||
.unwrap_or(*range);
|
||||
Edit::insertion(format!("{argument}, "), keyword.start())
|
||||
} else if let Some(last) = arguments.arguments_source_order().last() {
|
||||
// Case 1: existing arguments, so append after the last argument.
|
||||
let last = parenthesized_range(
|
||||
match last {
|
||||
ArgOrKeyword::Arg(arg) => arg.into(),
|
||||
ArgOrKeyword::Keyword(keyword) => (&keyword.value).into(),
|
||||
},
|
||||
last.value().into(),
|
||||
arguments.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
|
||||
@@ -453,6 +453,7 @@ impl<'a> Importer<'a> {
|
||||
names,
|
||||
level,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = stmt
|
||||
{
|
||||
if *level == 0
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::fix::{FixResult, fix_file};
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::{is_py314_support_enabled, is_unsupported_syntax_enabled};
|
||||
use crate::preview::is_py314_support_enabled;
|
||||
use crate::registry::{AsRule, Rule, RuleSet};
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
|
||||
@@ -447,11 +447,7 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
let syntax_errors = if is_unsupported_syntax_enabled(settings) {
|
||||
parsed.unsupported_syntax_errors()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
let syntax_errors = parsed.unsupported_syntax_errors();
|
||||
|
||||
diagnostics_to_messages(
|
||||
diagnostics,
|
||||
|
||||
@@ -7,17 +7,6 @@
|
||||
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
// https://github.com/astral-sh/ruff/issues/17412
|
||||
// https://github.com/astral-sh/ruff/issues/11934
|
||||
pub(crate) const fn is_semantic_errors_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/16429
|
||||
pub(crate) const fn is_unsupported_syntax_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
@@ -29,23 +18,11 @@ pub(crate) const fn is_full_path_match_source_strategy_enabled(settings: &Linter
|
||||
|
||||
// Rule-specific behavior
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/17136
|
||||
pub(crate) const fn is_shell_injection_only_trusted_input_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/15541
|
||||
pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/7501
|
||||
pub(crate) const fn is_bool_subtype_of_annotation_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/10759
|
||||
pub(crate) const fn is_comprehension_with_min_max_sum_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
@@ -63,21 +40,6 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/12676
|
||||
pub(crate) const fn is_fix_future_annotations_in_stub_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11074
|
||||
pub(crate) const fn is_only_add_return_none_at_end_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/12796
|
||||
pub(crate) const fn is_simplify_ternary_to_binary_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/16719
|
||||
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
@@ -94,23 +56,11 @@ pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSe
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/15313
|
||||
pub(crate) const fn is_defer_optional_to_up045_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/8473
|
||||
pub(crate) const fn is_unicode_to_unicode_confusables_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/17078
|
||||
pub(crate) const fn is_support_slices_in_literal_concatenation_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11370
|
||||
pub(crate) const fn is_undefined_export_in_dunder_init_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
@@ -121,16 +71,9 @@ pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) ->
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/17061
|
||||
pub(crate) const fn is_check_file_level_directives_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/17644
|
||||
pub(crate) const fn is_readlines_in_for_fix_safe_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
pub(crate) const fn multiple_with_statements_fix_safe_enabled(settings: &LinterSettings) -> bool {
|
||||
// https://github.com/astral-sh/ruff/pull/18208
|
||||
pub(crate) const fn is_multiple_with_statements_fix_safe_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -104,7 +104,12 @@ pub(crate) fn airflow_3_removal_expr(checker: &Checker, expr: &Expr) {
|
||||
check_name(checker, expr, *range);
|
||||
check_class_attribute(checker, attribute_expr);
|
||||
}
|
||||
Expr::Name(ExprName { id, ctx, range }) => {
|
||||
Expr::Name(ExprName {
|
||||
id,
|
||||
ctx,
|
||||
range,
|
||||
node_index: _,
|
||||
}) => {
|
||||
check_name(checker, expr, *range);
|
||||
if matches!(ctx, ExprContext::Store) {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind {
|
||||
@@ -375,8 +380,11 @@ fn check_context_key_usage_in_call(checker: &Checker, call_expr: &ExprCall) {
|
||||
}
|
||||
|
||||
for removed_key in REMOVED_CONTEXT_KEYS {
|
||||
let Some(Expr::StringLiteral(ExprStringLiteral { value, range })) =
|
||||
call_expr.arguments.find_positional(0)
|
||||
let Some(Expr::StringLiteral(ExprStringLiteral {
|
||||
value,
|
||||
range,
|
||||
node_index: _,
|
||||
})) = call_expr.arguments.find_positional(0)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -102,6 +102,7 @@ pub(crate) fn airflow_3_0_suggested_update_expr(checker: &Checker, expr: &Expr)
|
||||
id: _,
|
||||
ctx: _,
|
||||
range,
|
||||
node_index: _,
|
||||
}) => {
|
||||
check_name(checker, expr, *range);
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, slice: &Expr) {
|
||||
upper: Some(upper),
|
||||
step: None,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(i),
|
||||
|
||||
@@ -137,6 +137,7 @@ impl AutoPythonType {
|
||||
let expr = Expr::Name(ast::ExprName {
|
||||
id: Name::from(binding),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
Some((expr, vec![no_return_edit]))
|
||||
@@ -203,6 +204,7 @@ fn type_expr(python_type: PythonType) -> Option<Expr> {
|
||||
Expr::Name(ast::ExprName {
|
||||
id: name.into(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -562,7 +562,11 @@ fn is_stub_function(function_def: &ast::StmtFunctionDef, checker: &Checker) -> b
|
||||
fn is_empty_body(function_def: &ast::StmtFunctionDef) -> bool {
|
||||
function_def.body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
@@ -606,6 +610,7 @@ pub(crate) fn definition(
|
||||
|
||||
let ast::StmtFunctionDef {
|
||||
range: _,
|
||||
node_index: _,
|
||||
is_async: _,
|
||||
decorator_list,
|
||||
name,
|
||||
|
||||
@@ -104,7 +104,6 @@ mod tests {
|
||||
#[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))]
|
||||
#[test_case(Rule::SuspiciousNonCryptographicRandomUsage, Path::new("S311.py"))]
|
||||
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))]
|
||||
#[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -166,6 +166,7 @@ fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result<Option<u16>> {
|
||||
op,
|
||||
right,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
let Some(left_value) = parse_mask(left, semantic)? else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -7,7 +7,6 @@ use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::preview::is_shell_injection_only_trusted_input_enabled;
|
||||
use crate::{
|
||||
checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal,
|
||||
};
|
||||
@@ -325,9 +324,7 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
// S603
|
||||
_ => {
|
||||
if !is_trusted_input(arg)
|
||||
|| !is_shell_injection_only_trusted_input_enabled(checker.settings)
|
||||
{
|
||||
if !is_trusted_input(arg) {
|
||||
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
|
||||
checker.report_diagnostic(
|
||||
SubprocessWithoutShellEqualsTrue,
|
||||
|
||||
@@ -789,9 +789,9 @@ impl Violation for SuspiciousXMLPullDOMUsage {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Deprecation
|
||||
/// ## Removed
|
||||
///
|
||||
/// This rule was deprecated as the `lxml` library has been modified to address
|
||||
/// This rule was removed as the `lxml` library has been modified to address
|
||||
/// known vulnerabilities and unsafe defaults. As such, the `defusedxml`
|
||||
/// library is no longer necessary, `defusedxml` has [deprecated] its `lxml`
|
||||
/// module.
|
||||
|
||||
@@ -106,74 +106,6 @@ S603.py:21:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
23 | # Literals are fine, they're trusted.
|
||||
|
|
||||
|
||||
S603.py:24:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
23 | # Literals are fine, they're trusted.
|
||||
24 | run("true")
|
||||
| ^^^ S603
|
||||
25 | Popen(["true"])
|
||||
26 | Popen("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:25:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
23 | # Literals are fine, they're trusted.
|
||||
24 | run("true")
|
||||
25 | Popen(["true"])
|
||||
| ^^^^^ S603
|
||||
26 | Popen("true", shell=False)
|
||||
27 | call("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:26:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
24 | run("true")
|
||||
25 | Popen(["true"])
|
||||
26 | Popen("true", shell=False)
|
||||
| ^^^^^ S603
|
||||
27 | call("true", shell=False)
|
||||
28 | check_call("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:27:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
25 | Popen(["true"])
|
||||
26 | Popen("true", shell=False)
|
||||
27 | call("true", shell=False)
|
||||
| ^^^^ S603
|
||||
28 | check_call("true", shell=False)
|
||||
29 | check_output("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:28:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
26 | Popen("true", shell=False)
|
||||
27 | call("true", shell=False)
|
||||
28 | check_call("true", shell=False)
|
||||
| ^^^^^^^^^^ S603
|
||||
29 | check_output("true", shell=False)
|
||||
30 | run("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:29:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
27 | call("true", shell=False)
|
||||
28 | check_call("true", shell=False)
|
||||
29 | check_output("true", shell=False)
|
||||
| ^^^^^^^^^^^^ S603
|
||||
30 | run("true", shell=False)
|
||||
|
|
||||
|
||||
S603.py:30:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
28 | check_call("true", shell=False)
|
||||
29 | check_output("true", shell=False)
|
||||
30 | run("true", shell=False)
|
||||
| ^^^ S603
|
||||
31 |
|
||||
32 | # Not through assignments though.
|
||||
|
|
||||
|
||||
S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
32 | # Not through assignments though.
|
||||
@@ -184,15 +116,6 @@ S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
36 | # Instant named expressions are fine.
|
||||
|
|
||||
|
||||
S603.py:37:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
36 | # Instant named expressions are fine.
|
||||
37 | run(c := "true")
|
||||
| ^^^ S603
|
||||
38 |
|
||||
39 | # But non-instant are not.
|
||||
|
|
||||
|
||||
S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
39 | # But non-instant are not.
|
||||
@@ -200,20 +123,3 @@ S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
41 | run(e)
|
||||
| ^^^ S603
|
||||
|
|
||||
|
||||
S603.py:46:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
44 | # https://github.com/astral-sh/ruff/issues/17798
|
||||
45 | # Tuple literals are trusted
|
||||
46 | check_output(("literal", "cmd", "using", "tuple"), text=True)
|
||||
| ^^^^^^^^^^^^ S603
|
||||
47 | Popen(("literal", "cmd", "using", "tuple"))
|
||||
|
|
||||
|
||||
S603.py:47:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
45 | # Tuple literals are trusted
|
||||
46 | check_output(("literal", "cmd", "using", "tuple"), text=True)
|
||||
47 | Popen(("literal", "cmd", "using", "tuple"))
|
||||
| ^^^^^ S603
|
||||
|
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S603.py:5:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
3 | # Different Popen wrappers are checked.
|
||||
4 | a = input()
|
||||
5 | Popen(a, shell=False)
|
||||
| ^^^^^ S603
|
||||
6 | call(a, shell=False)
|
||||
7 | check_call(a, shell=False)
|
||||
|
|
||||
|
||||
S603.py:6:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
4 | a = input()
|
||||
5 | Popen(a, shell=False)
|
||||
6 | call(a, shell=False)
|
||||
| ^^^^ S603
|
||||
7 | check_call(a, shell=False)
|
||||
8 | check_output(a, shell=False)
|
||||
|
|
||||
|
||||
S603.py:7:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
5 | Popen(a, shell=False)
|
||||
6 | call(a, shell=False)
|
||||
7 | check_call(a, shell=False)
|
||||
| ^^^^^^^^^^ S603
|
||||
8 | check_output(a, shell=False)
|
||||
9 | run(a, shell=False)
|
||||
|
|
||||
|
||||
S603.py:8:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
6 | call(a, shell=False)
|
||||
7 | check_call(a, shell=False)
|
||||
8 | check_output(a, shell=False)
|
||||
| ^^^^^^^^^^^^ S603
|
||||
9 | run(a, shell=False)
|
||||
|
|
||||
|
||||
S603.py:9:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
7 | check_call(a, shell=False)
|
||||
8 | check_output(a, shell=False)
|
||||
9 | run(a, shell=False)
|
||||
| ^^^ S603
|
||||
10 |
|
||||
11 | # Falsey values are treated as false.
|
||||
|
|
||||
|
||||
S603.py:12:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
11 | # Falsey values are treated as false.
|
||||
12 | Popen(a, shell=0)
|
||||
| ^^^^^ S603
|
||||
13 | Popen(a, shell=[])
|
||||
14 | Popen(a, shell={})
|
||||
|
|
||||
|
||||
S603.py:13:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
11 | # Falsey values are treated as false.
|
||||
12 | Popen(a, shell=0)
|
||||
13 | Popen(a, shell=[])
|
||||
| ^^^^^ S603
|
||||
14 | Popen(a, shell={})
|
||||
15 | Popen(a, shell=None)
|
||||
|
|
||||
|
||||
S603.py:14:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
12 | Popen(a, shell=0)
|
||||
13 | Popen(a, shell=[])
|
||||
14 | Popen(a, shell={})
|
||||
| ^^^^^ S603
|
||||
15 | Popen(a, shell=None)
|
||||
|
|
||||
|
||||
S603.py:15:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
13 | Popen(a, shell=[])
|
||||
14 | Popen(a, shell={})
|
||||
15 | Popen(a, shell=None)
|
||||
| ^^^^^ S603
|
||||
16 |
|
||||
17 | # Unknown values are treated as falsey.
|
||||
|
|
||||
|
||||
S603.py:18:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
17 | # Unknown values are treated as falsey.
|
||||
18 | Popen(a, shell=True if True else False)
|
||||
| ^^^^^ S603
|
||||
19 |
|
||||
20 | # No value is also caught.
|
||||
|
|
||||
|
||||
S603.py:21:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
20 | # No value is also caught.
|
||||
21 | Popen(a)
|
||||
| ^^^^^ S603
|
||||
22 |
|
||||
23 | # Literals are fine, they're trusted.
|
||||
|
|
||||
|
||||
S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
32 | # Not through assignments though.
|
||||
33 | cmd = ["true"]
|
||||
34 | run(cmd)
|
||||
| ^^^ S603
|
||||
35 |
|
||||
36 | # Instant named expressions are fine.
|
||||
|
|
||||
|
||||
S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input
|
||||
|
|
||||
39 | # But non-instant are not.
|
||||
40 | (e := "echo")
|
||||
41 | run(e)
|
||||
| ^^^ S603
|
||||
|
|
||||
@@ -12,7 +12,6 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
@@ -29,24 +28,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_boolean_trap").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_allowed_callable() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -7,12 +7,12 @@ use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_bool_subtype_of_annotation_enabled;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
/// as determined by the presence of a `bool` type hint.
|
||||
/// as determined by the presence of a type hint containing `bool` as an
|
||||
/// evident subtype - e.g. `bool`, `bool | int`, `typing.Optional[bool]`, etc.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling a function with boolean positional arguments is confusing as the
|
||||
@@ -30,9 +30,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// Dunder methods that define operators are exempt from this rule, as are
|
||||
/// setters and `@override` definitions.
|
||||
///
|
||||
/// In [preview], this rule will also flag annotations that include boolean
|
||||
/// variants, like `bool | int`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
@@ -96,8 +93,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BooleanTypeHintPositionalArgument;
|
||||
|
||||
@@ -128,14 +123,8 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
let Some(annotation) = parameter.annotation() else {
|
||||
continue;
|
||||
};
|
||||
if is_bool_subtype_of_annotation_enabled(checker.settings) {
|
||||
if !match_annotation_to_complex_bool(annotation, checker.semantic()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if !match_annotation_to_literal_bool(annotation) {
|
||||
continue;
|
||||
}
|
||||
if !match_annotation_to_complex_bool(annotation, checker.semantic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow Boolean type hints in setters.
|
||||
@@ -161,17 +150,6 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the annotation is a boolean type hint (e.g., `bool`).
|
||||
fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
|
||||
match annotation {
|
||||
// Ex) `True`
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
// Ex) `"True"`
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the annotation is a boolean type hint (e.g., `bool`), or a type hint that
|
||||
/// includes boolean as a variant (e.g., `bool | int`).
|
||||
fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
@@ -89,3 +88,17 @@ FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition
|
||||
| ^^^^^ FBT001
|
||||
91 | pass
|
||||
|
|
||||
|
||||
FBT.py:100:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
100 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
| ^ FBT001
|
||||
101 | pass
|
||||
|
|
||||
|
||||
FBT.py:104:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
104 | def func(x: bool | str):
|
||||
| ^ FBT001
|
||||
105 | pass
|
||||
|
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
2 | posonly_nohint,
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^ FBT001
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
6 | /,
|
||||
|
|
||||
|
||||
FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
6 | /,
|
||||
7 | offset,
|
||||
|
|
||||
|
||||
FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
8 | posorkw_nonvalued_nohint,
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
|
|
||||
|
||||
FBT.py:11:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
|
|
||||
|
||||
FBT.py:14:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
|
|
||||
|
||||
FBT.py:15:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
|
|
||||
|
||||
FBT.py:18:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
20 | *,
|
||||
|
|
||||
|
||||
FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
20 | *,
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
89 | # FBT001: Boolean positional arg in function definition
|
||||
90 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
91 | pass
|
||||
|
|
||||
|
||||
FBT.py:100:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
100 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
| ^ FBT001
|
||||
101 | pass
|
||||
|
|
||||
|
||||
FBT.py:104:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
104 | def func(x: bool | str):
|
||||
| ^ FBT001
|
||||
105 | pass
|
||||
|
|
||||
@@ -131,7 +131,11 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel)
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
|
||||
@@ -52,11 +52,13 @@ impl AlwaysFixableViolation for AssertFalse {
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::Raise(ast::StmtRaise {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
exc: Some(Box::new(Expr::Call(ast::ExprCall {
|
||||
func: Box::new(Expr::Name(ast::ExprName {
|
||||
id: "AssertionError".into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
})),
|
||||
arguments: Arguments {
|
||||
args: if let Some(msg) = msg {
|
||||
@@ -66,8 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
},
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
}))),
|
||||
cause: None,
|
||||
})
|
||||
|
||||
@@ -63,6 +63,7 @@ pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) {
|
||||
func,
|
||||
arguments,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = &item.context_expr
|
||||
else {
|
||||
continue;
|
||||
|
||||
@@ -113,6 +113,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
elts: elts.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
parenthesized: true,
|
||||
}
|
||||
.into()
|
||||
|
||||
@@ -45,7 +45,12 @@ pub(crate) fn f_string_docstring(checker: &Checker, body: &[Stmt]) {
|
||||
let Some(stmt) = body.first() else {
|
||||
return;
|
||||
};
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
let Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = stmt
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !value.is_f_string_expr() {
|
||||
|
||||
@@ -111,6 +111,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value: Some(value),
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
// Mark `return lambda: x` as safe.
|
||||
if value.is_lambda_expr() {
|
||||
@@ -128,6 +129,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
func,
|
||||
arguments,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
match func.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
@@ -167,6 +169,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if !self.safe_functions.contains(&expr) {
|
||||
// Collect all loaded variable names.
|
||||
|
||||
@@ -60,6 +60,7 @@ pub(crate) fn loop_iterator_mutation(checker: &Checker, stmt_for: &StmtFor) {
|
||||
orelse: _,
|
||||
is_async: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = stmt_for;
|
||||
|
||||
let (index, target, iter) = match iter.as_ref() {
|
||||
@@ -170,6 +171,7 @@ impl<'a> LoopMutationsVisitor<'a> {
|
||||
for target in targets {
|
||||
if let Expr::Subscript(ExprSubscript {
|
||||
range: _,
|
||||
node_index: _,
|
||||
value,
|
||||
slice: _,
|
||||
ctx: _,
|
||||
@@ -188,6 +190,7 @@ impl<'a> LoopMutationsVisitor<'a> {
|
||||
for target in targets {
|
||||
if let Expr::Subscript(ExprSubscript {
|
||||
range: _,
|
||||
node_index: _,
|
||||
value,
|
||||
slice,
|
||||
ctx: _,
|
||||
@@ -217,6 +220,7 @@ impl<'a> LoopMutationsVisitor<'a> {
|
||||
fn handle_call(&mut self, func: &Expr) {
|
||||
if let Expr::Attribute(ExprAttribute {
|
||||
range,
|
||||
node_index: _,
|
||||
value,
|
||||
attr,
|
||||
ctx: _,
|
||||
@@ -237,7 +241,11 @@ impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match stmt {
|
||||
// Ex) `del items[0]`
|
||||
Stmt::Delete(StmtDelete { range, targets }) => {
|
||||
Stmt::Delete(StmtDelete {
|
||||
range,
|
||||
targets,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.handle_delete(*range, targets);
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
@@ -285,7 +293,6 @@ impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> {
|
||||
if let Some(mutations) = self.mutations.get_mut(&self.branch) {
|
||||
mutations.clear();
|
||||
}
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
// Avoid recursion for class and function definitions.
|
||||
|
||||
@@ -97,6 +97,7 @@ impl<'a> Visitor<'a> for NameFinder<'a> {
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
visitor::walk_expr(self, body);
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ impl StatementVisitor<'_> for ReturnInGeneratorVisitor {
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value: Some(_),
|
||||
range,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.return_ = Some(*range);
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
// base if plus branches
|
||||
let mut if_stack = Vec::with_capacity(1 + elif_else_clauses.len());
|
||||
@@ -179,6 +180,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
subject,
|
||||
cases,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.counter_stack.push(Vec::with_capacity(cases.len()));
|
||||
self.visit_expr(subject);
|
||||
@@ -210,7 +212,11 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
Stmt::Continue(_) | Stmt::Break(_) => {
|
||||
self.reset_usage_count();
|
||||
}
|
||||
Stmt::Return(ast::StmtReturn { value, range: _ }) => {
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if let Some(expr) = value {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -250,11 +256,13 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
})
|
||||
| Expr::SetComp(ast::ExprSetComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
for comprehension in generators {
|
||||
self.visit_comprehension(comprehension);
|
||||
@@ -270,6 +278,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
value,
|
||||
generators,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
for comprehension in generators {
|
||||
self.visit_comprehension(comprehension);
|
||||
|
||||
@@ -53,9 +53,11 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
|
||||
attr: Identifier::new(name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
})],
|
||||
value: Box::new(value.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
generator.stmt(&stmt)
|
||||
}
|
||||
@@ -92,6 +94,7 @@ pub(crate) fn setattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr,
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value: child,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = checker.semantic().current_statement()
|
||||
{
|
||||
if expr == child.as_ref() {
|
||||
|
||||
@@ -37,11 +37,10 @@ B028.py:9:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
7 7 |
|
||||
8 8 | warnings.warn("test", DeprecationWarning)
|
||||
9 |-warnings.warn("test", DeprecationWarning, source=None)
|
||||
10 9 | warnings.warn("test", DeprecationWarning, source=None, stacklevel=2)
|
||||
10 |+warnings.warn("test", DeprecationWarning, source=None, stacklevel=2)
|
||||
9 |+warnings.warn("test", DeprecationWarning, stacklevel=2, source=None)
|
||||
10 10 | warnings.warn("test", DeprecationWarning, source=None, stacklevel=2)
|
||||
11 11 | warnings.warn("test", DeprecationWarning, stacklevel=1)
|
||||
12 12 | warnings.warn("test", DeprecationWarning, 1)
|
||||
13 13 | warnings.warn("test", category=DeprecationWarning, stacklevel=1)
|
||||
|
||||
B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
|
|
||||
@@ -59,7 +58,7 @@ B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
24 24 | DeprecationWarning,
|
||||
25 25 | # some comments here
|
||||
26 |- source = None # no trailing comma
|
||||
26 |+ source = None, stacklevel=2 # no trailing comma
|
||||
26 |+ stacklevel=2, source = None # no trailing comma
|
||||
27 27 | )
|
||||
28 28 |
|
||||
29 29 | # https://github.com/astral-sh/ruff/issues/18011
|
||||
@@ -80,7 +79,7 @@ B028.py:32:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
30 30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),))
|
||||
31 31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value
|
||||
32 |-warnings.warn("test", skip_file_prefixes=())
|
||||
32 |+warnings.warn("test", skip_file_prefixes=(), stacklevel=2)
|
||||
32 |+warnings.warn("test", stacklevel=2, skip_file_prefixes=())
|
||||
33 33 |
|
||||
34 34 | _my_prefixes = ("this","that")
|
||||
35 35 | warnings.warn("test", skip_file_prefixes = _my_prefixes)
|
||||
|
||||
@@ -176,14 +176,17 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
|
||||
},
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
};
|
||||
Expr::Call(ExprCall {
|
||||
func: Box::new(Expr::Name(ExprName {
|
||||
id: "dict.fromkeys".into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
})),
|
||||
arguments: args,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ pub(crate) fn unnecessary_list_call(checker: &Checker, expr: &Expr, call: &ExprC
|
||||
func,
|
||||
arguments,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = call;
|
||||
|
||||
if !arguments.keywords.is_empty() {
|
||||
@@ -60,6 +61,7 @@ pub(crate) fn unnecessary_list_call(checker: &Checker, expr: &Expr, call: &ExprC
|
||||
|
||||
let Arguments {
|
||||
range: _,
|
||||
node_index: _,
|
||||
args,
|
||||
keywords: _,
|
||||
} = arguments;
|
||||
|
||||
@@ -52,6 +52,7 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &Checker, call: &ast::Expr
|
||||
upper,
|
||||
step,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = slice.as_ref()
|
||||
else {
|
||||
return;
|
||||
@@ -66,6 +67,7 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &Checker, call: &ast::Expr
|
||||
op: UnaryOp::USub,
|
||||
operand,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = step.as_ref()
|
||||
else {
|
||||
return;
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
op: BoolOp::Or,
|
||||
values,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = expr
|
||||
else {
|
||||
return;
|
||||
@@ -86,8 +87,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = &call
|
||||
else {
|
||||
continue;
|
||||
@@ -145,8 +148,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
args,
|
||||
keywords: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
},
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = expr
|
||||
else {
|
||||
unreachable!(
|
||||
@@ -173,18 +178,21 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
parenthesized: true,
|
||||
});
|
||||
let node1 = Expr::Name(ast::ExprName {
|
||||
id: arg_name.into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let node2 = Expr::Attribute(ast::ExprAttribute {
|
||||
value: Box::new(node1),
|
||||
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let node3 = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(node2),
|
||||
@@ -192,8 +200,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
args: Box::from([node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let call = node3;
|
||||
|
||||
@@ -213,6 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
})
|
||||
.collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let bool_op = node;
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -63,6 +63,7 @@ pub(crate) fn reimplemented_container_builtin(checker: &Checker, expr: &ExprLamb
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = expr;
|
||||
|
||||
if parameters.is_some() {
|
||||
|
||||
@@ -11,7 +11,6 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pep8_naming;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
@@ -172,22 +171,4 @@ mod tests {
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_pyi").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +142,18 @@ pub(crate) fn custom_type_var_instead_of_self(checker: &Checker, binding: &Bindi
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(self_or_cls_annotation) = self_or_cls_parameter.annotation() else {
|
||||
let Some(self_or_cls_annotation_unchecked) = self_or_cls_parameter.annotation() else {
|
||||
return;
|
||||
};
|
||||
let self_or_cls_annotation = match self_or_cls_annotation_unchecked {
|
||||
ast::Expr::StringLiteral(literal_expr) => {
|
||||
let Ok(parsed_expr) = checker.parse_type_annotation(literal_expr) else {
|
||||
return;
|
||||
};
|
||||
parsed_expr.expression()
|
||||
}
|
||||
_ => self_or_cls_annotation_unchecked,
|
||||
};
|
||||
let Some(parent_class) = current_scope.kind.as_class() else {
|
||||
return;
|
||||
};
|
||||
@@ -202,7 +211,7 @@ pub(crate) fn custom_type_var_instead_of_self(checker: &Checker, binding: &Bindi
|
||||
function_def,
|
||||
custom_typevar,
|
||||
self_or_cls_parameter,
|
||||
self_or_cls_annotation,
|
||||
self_or_cls_annotation_unchecked,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,12 +88,14 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: unique_nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})
|
||||
}),
|
||||
value: subscript.value.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
let fix = Fix::applicable_edit(
|
||||
|
||||
@@ -165,6 +165,7 @@ fn generate_pep604_fix(
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(right.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
}))
|
||||
} else {
|
||||
Some(right.clone())
|
||||
|
||||
@@ -347,6 +347,7 @@ fn check_positional_args_for_overloaded_method(
|
||||
// If any overloads have any variadic arguments, don't do any checking
|
||||
let Parameters {
|
||||
range: _,
|
||||
node_index: _,
|
||||
posonlyargs,
|
||||
args,
|
||||
vararg: None,
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::StmtImportFrom;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::{Fix, FixAvailability, Violation};
|
||||
use crate::{checkers::ast::Checker, fix, preview::is_fix_future_annotations_in_stub_enabled};
|
||||
use crate::{checkers::ast::Checker, fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of the `from __future__ import annotations` import
|
||||
@@ -55,20 +55,18 @@ pub(crate) fn from_future_import(checker: &Checker, target: &StmtImportFrom) {
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(FutureAnnotationsInStub, *range);
|
||||
|
||||
if is_fix_future_annotations_in_stub_enabled(checker.settings) {
|
||||
let stmt = checker.semantic().current_statement();
|
||||
let stmt = checker.semantic().current_statement();
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let edit = fix::edits::remove_unused_imports(
|
||||
std::iter::once("annotations"),
|
||||
stmt,
|
||||
None,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
checker.indexer(),
|
||||
)?;
|
||||
diagnostic.try_set_fix(|| {
|
||||
let edit = fix::edits::remove_unused_imports(
|
||||
std::iter::once("annotations"),
|
||||
stmt,
|
||||
None,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
checker.indexer(),
|
||||
)?;
|
||||
|
||||
Ok(Fix::safe_edit(edit))
|
||||
});
|
||||
}
|
||||
Ok(Fix::safe_edit(edit))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// ## Why is this bad?
|
||||
/// If `Generic[]` is not the final class in the bases tuple, unexpected
|
||||
/// behaviour can occur at runtime (See [this CPython issue][1] for an example).
|
||||
/// The rule is also applied to stub files, but, unlike at runtime,
|
||||
/// in stubs it is purely enforced for stylistic consistency.
|
||||
///
|
||||
/// The rule is also applied to stub files, where it won't cause issues at
|
||||
/// runtime. This is because type checkers may not be able to infer an
|
||||
/// accurate [MRO] for the class, which could lead to unexpected or
|
||||
/// inaccurate results when they analyze your code.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
@@ -43,10 +46,23 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// ):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// This rule's fix is always unsafe because reordering base classes can change
|
||||
/// the behavior of the code by modifying the class's MRO. The fix will also
|
||||
/// delete trailing comments after the `Generic` base class in multi-line base
|
||||
/// class lists, if any are present.
|
||||
///
|
||||
/// ## Fix availability
|
||||
///
|
||||
/// This rule's fix is only available when there are no `*args` present in the base class list.
|
||||
///
|
||||
/// ## References
|
||||
/// - [`typing.Generic` documentation](https://docs.python.org/3/library/typing.html#typing.Generic)
|
||||
///
|
||||
/// [1]: https://github.com/python/cpython/issues/106102
|
||||
/// [MRO]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct GenericNotLastBaseClass;
|
||||
|
||||
@@ -94,6 +110,22 @@ pub(crate) fn generic_not_last_base_class(checker: &Checker, class_def: &ast::St
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(GenericNotLastBaseClass, bases.range());
|
||||
|
||||
// Avoid suggesting a fix if any of the arguments is starred. This avoids tricky syntax errors
|
||||
// in cases like
|
||||
//
|
||||
// ```python
|
||||
// class C3(Generic[T], metaclass=type, *[str]): ...
|
||||
// ```
|
||||
//
|
||||
// where we would naively try to put `Generic[T]` after `*[str]`, which is also after a keyword
|
||||
// argument, causing the error.
|
||||
if bases
|
||||
.arguments_source_order()
|
||||
.any(|arg| arg.value().is_starred_expr())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No fix if multiple `Generic[]`s are seen in the class bases.
|
||||
if generic_base_iter.next().is_none() {
|
||||
diagnostic.try_set_fix(|| generate_fix(generic_base, bases, checker));
|
||||
@@ -116,5 +148,5 @@ fn generate_fix(
|
||||
source,
|
||||
);
|
||||
|
||||
Ok(Fix::safe_edits(deletion, [insertion]))
|
||||
Ok(Fix::unsafe_edits(deletion, [insertion]))
|
||||
}
|
||||
|
||||
@@ -133,14 +133,17 @@ fn generate_union_fix(
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
value: Box::new(Expr::Name(ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
|
||||
@@ -59,7 +59,12 @@ pub(crate) fn non_empty_stub_body(checker: &Checker, body: &[Stmt]) {
|
||||
}
|
||||
|
||||
// Ignore `...` (the desired case).
|
||||
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = stmt
|
||||
{
|
||||
if value.is_ellipsis_literal_expr() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,11 +205,13 @@ fn create_fix(
|
||||
let new_literal_expr = Expr::Subscript(ast::ExprSubscript {
|
||||
value: Box::new(literal_subscript.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
slice: Box::new(if literal_elements.len() > 1 {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: literal_elements.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})
|
||||
@@ -233,6 +235,7 @@ fn create_fix(
|
||||
UnionKind::BitOr => {
|
||||
let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
|
||||
let content = checker.generator().expr(&union_expr);
|
||||
|
||||
@@ -252,6 +252,7 @@ fn generate_pep604_fix(
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(right.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
}))
|
||||
} else {
|
||||
Some(right.clone())
|
||||
|
||||
@@ -300,7 +300,11 @@ fn is_valid_default_value_with_annotation(
|
||||
}
|
||||
Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
| Expr::Set(ast::ExprSet { elts, range: _ }) => {
|
||||
| Expr::Set(ast::ExprSet {
|
||||
elts,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
return allow_container
|
||||
&& elts.len() <= 10
|
||||
&& elts
|
||||
@@ -320,6 +324,7 @@ fn is_valid_default_value_with_annotation(
|
||||
op: UnaryOp::USub,
|
||||
operand,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
match operand.as_ref() {
|
||||
// Ex) `-1`, `-3.14`, `2j`
|
||||
@@ -342,6 +347,7 @@ fn is_valid_default_value_with_annotation(
|
||||
op: Operator::Add | Operator::Sub,
|
||||
right,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
// Ex) `1 + 2j`, `1 - 2j`, `-1 - 2j`, `-1 + 2j`
|
||||
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
@@ -360,6 +366,7 @@ fn is_valid_default_value_with_annotation(
|
||||
op: UnaryOp::USub,
|
||||
operand,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = left.as_ref()
|
||||
{
|
||||
// Ex) `-1 + 2j`, `-1 - 2j`
|
||||
@@ -398,6 +405,7 @@ fn is_valid_pep_604_union(annotation: &Expr) -> bool {
|
||||
op: Operator::BitOr,
|
||||
right,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => is_valid_pep_604_union_member(left) && is_valid_pep_604_union_member(right),
|
||||
Expr::Name(_) | Expr::Subscript(_) | Expr::Attribute(_) | Expr::NoneLiteral(_) => true,
|
||||
_ => false,
|
||||
@@ -410,6 +418,7 @@ fn is_valid_pep_604_union(annotation: &Expr) -> bool {
|
||||
op: Operator::BitOr,
|
||||
right,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = annotation
|
||||
else {
|
||||
return false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user