Compare commits
160 Commits
cjm/colors
...
v0.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77c93fd63c | ||
|
|
1c9f5e3001 | ||
|
|
263a0d25ed | ||
|
|
4738e19974 | ||
|
|
f428bd5052 | ||
|
|
4690890e9f | ||
|
|
19baabba58 | ||
|
|
cee38f39df | ||
|
|
e3fde28146 | ||
|
|
1c8849f9a8 | ||
|
|
37af6e6147 | ||
|
|
92814fd99b | ||
|
|
c9c2e7b978 | ||
|
|
51dec8d95b | ||
|
|
7c8c1c71a3 | ||
|
|
455d22cdc8 | ||
|
|
35ca887e02 | ||
|
|
f5c7a62aa6 | ||
|
|
38d2562f41 | ||
|
|
7eba967e16 | ||
|
|
5b81b8368d | ||
|
|
c30735d4a7 | ||
|
|
62478c3070 | ||
|
|
111bbc61f6 | ||
|
|
925c7f8dd3 | ||
|
|
5b4c8a7c5f | ||
|
|
647548b5e7 | ||
|
|
a9919707d4 | ||
|
|
5dcb1d9e8c | ||
|
|
0b92f450ca | ||
|
|
54d42957b0 | ||
|
|
d3ed0ad68c | ||
|
|
01678a990c | ||
|
|
45692ce89f | ||
|
|
4246111f67 | ||
|
|
9924bd774a | ||
|
|
fc7f07bca7 | ||
|
|
bd6ca3d586 | ||
|
|
23f8e1c3c8 | ||
|
|
a68938897d | ||
|
|
d544199272 | ||
|
|
c80b9a4a90 | ||
|
|
99f7f94538 | ||
|
|
7b3c92a979 | ||
|
|
fdbcb62adc | ||
|
|
0ff25a540c | ||
|
|
34873ec009 | ||
|
|
d3cd61f804 | ||
|
|
9b80cc09ee | ||
|
|
9bb23b0a38 | ||
|
|
06c248a126 | ||
|
|
27902b7130 | ||
|
|
97acf1d59b | ||
|
|
adf63d9013 | ||
|
|
5d3c9f2637 | ||
|
|
33529c049e | ||
|
|
e751b4ea82 | ||
|
|
25a9131109 | ||
|
|
b7066e64e7 | ||
|
|
6c4d779140 | ||
|
|
8020d486f6 | ||
|
|
13ffb5bc19 | ||
|
|
e09180b1df | ||
|
|
2cc487eb22 | ||
|
|
5da7299b32 | ||
|
|
4d8890eef5 | ||
|
|
9f01ac3f87 | ||
|
|
b23414e3cc | ||
|
|
1480d72643 | ||
|
|
06b3e376ac | ||
|
|
e8b1125b30 | ||
|
|
16cc9bd78d | ||
|
|
0a6327418d | ||
|
|
518b29a9ef | ||
|
|
caae8d2c68 | ||
|
|
2971655b28 | ||
|
|
f48a794125 | ||
|
|
2882604451 | ||
|
|
eab3c4e334 | ||
|
|
cffc55576f | ||
|
|
4284e079b5 | ||
|
|
65edbfe62f | ||
|
|
45db695c47 | ||
|
|
1801798e85 | ||
|
|
d4e140d47f | ||
|
|
f779babc5f | ||
|
|
effd5188c9 | ||
|
|
6dccbd2b58 | ||
|
|
0c8ba32819 | ||
|
|
4ac523c19d | ||
|
|
f9214f95bb | ||
|
|
49d9ad4c7e | ||
|
|
c2210359e7 | ||
|
|
670d66f54c | ||
|
|
cbd500141f | ||
|
|
b62aeb39d2 | ||
|
|
cdbd754870 | ||
|
|
91efca1837 | ||
|
|
09ae2341e9 | ||
|
|
f07af6fb63 | ||
|
|
b11d17f65c | ||
|
|
b4c7c55ddd | ||
|
|
0f01713257 | ||
|
|
ed9a92d915 | ||
|
|
6da4ea6116 | ||
|
|
f9a828f493 | ||
|
|
812b0976a9 | ||
|
|
b356c4376c | ||
|
|
85ca5b7eed | ||
|
|
c2421068bc | ||
|
|
e9870fe468 | ||
|
|
a013050c11 | ||
|
|
2e37cf6b3b | ||
|
|
a9e4393008 | ||
|
|
312f43475f | ||
|
|
563daa8a86 | ||
|
|
7ae15c6e0a | ||
|
|
03899dcba3 | ||
|
|
25f5a8b201 | ||
|
|
e7d1d43f39 | ||
|
|
9b9098c3dc | ||
|
|
0cc154c2a9 | ||
|
|
4e8a84617c | ||
|
|
ffea1bb0a3 | ||
|
|
ac14d187c6 | ||
|
|
1eee6f16e4 | ||
|
|
de46a36bbc | ||
|
|
dbf8d0c82c | ||
|
|
02e88fdbb1 | ||
|
|
42d52ebbec | ||
|
|
3fd22973da | ||
|
|
e13e57e024 | ||
|
|
c3e28f9d55 | ||
|
|
a188ba5c26 | ||
|
|
86419c8ab9 | ||
|
|
a9ebfe6ec0 | ||
|
|
0a50874c01 | ||
|
|
6050bab5db | ||
|
|
2a51dcfdf7 | ||
|
|
86588695e3 | ||
|
|
47e0cb8985 | ||
|
|
388658efdb | ||
|
|
3194f90db1 | ||
|
|
ee4bff3475 | ||
|
|
7fb012d0df | ||
|
|
44459f92ef | ||
|
|
1dc93107dc | ||
|
|
7fb5f47efe | ||
|
|
83db62bcda | ||
|
|
b45fd61ec5 | ||
|
|
323264dec2 | ||
|
|
c11e6d709c | ||
|
|
1b31d4e9f1 | ||
|
|
a184dc68f5 | ||
|
|
a4ee9c1978 | ||
|
|
c2790f912b | ||
|
|
2e7a1a4cb1 | ||
|
|
d050d6da2e | ||
|
|
fd8da66fcb | ||
|
|
d02b1069b5 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -8,5 +8,7 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py 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
|
||||
|
||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||
|
||||
ruff.schema.json linguist-generated=true text=auto eol=lf
|
||||
*.md.snap linguist-language=Markdown
|
||||
|
||||
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -5,11 +5,13 @@
|
||||
# - The '*' pattern is global owners.
|
||||
# - Order is important. The last matching pattern has the most precedence.
|
||||
|
||||
# Jupyter
|
||||
/crates/ruff_linter/src/jupyter/ @dhruvmanila
|
||||
/crates/ruff_notebook/ @dhruvmanila
|
||||
/crates/ruff_formatter/ @MichaReiser
|
||||
/crates/ruff_python_formatter/ @MichaReiser
|
||||
/crates/ruff_python_parser/ @MichaReiser
|
||||
/crates/ruff_python_parser/ @MichaReiser @dhruvmanila
|
||||
|
||||
# flake8-pyi
|
||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||
|
||||
# Script for fuzzing the parser
|
||||
/scripts/fuzz-parser/ @AlexWaygood
|
||||
|
||||
10
.github/renovate.json5
vendored
10
.github/renovate.json5
vendored
@@ -4,7 +4,8 @@
|
||||
suppressNotifications: ["prEditedNotification"],
|
||||
extends: ["config:recommended"],
|
||||
labels: ["internal"],
|
||||
schedule: ["on Monday"],
|
||||
schedule: ["before 4am on Monday"],
|
||||
semanticCommits: "disabled",
|
||||
separateMajorMinor: false,
|
||||
prHourlyLimit: 10,
|
||||
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"],
|
||||
@@ -52,6 +53,13 @@
|
||||
matchPackagePatterns: ["strum"],
|
||||
description: "Weekly update of strum dependencies",
|
||||
},
|
||||
{
|
||||
groupName: "ESLint",
|
||||
matchManagers: ["npm"],
|
||||
matchPackageNames: ["eslint"],
|
||||
allowedVersions: "<9",
|
||||
description: "Constraint ESLint to version 8 until TypeScript-eslint supports ESLint 9", // https://github.com/typescript-eslint/typescript-eslint/issues/8211
|
||||
},
|
||||
],
|
||||
vulnerabilityAlerts: {
|
||||
commitMessageSuffix: "",
|
||||
|
||||
51
.github/workflows/ci.yaml
vendored
51
.github/workflows/ci.yaml
vendored
@@ -23,6 +23,8 @@ jobs:
|
||||
name: "Determine changes"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Flag that is raised when any code that affects parser is changed
|
||||
parser: ${{ steps.changed.outputs.parser_any_changed }}
|
||||
# Flag that is raised when any code that affects linter is changed
|
||||
linter: ${{ steps.changed.outputs.linter_any_changed }}
|
||||
# Flag that is raised when any code that affects formatter is changed
|
||||
@@ -39,6 +41,17 @@ jobs:
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
parser:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- crates/ruff_python_trivia/**
|
||||
- crates/ruff_source_file/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_python_parser/**
|
||||
- scripts/fuzz-parser/**
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
linter:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
@@ -200,6 +213,38 @@ jobs:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- run: cargo fuzz build -s none
|
||||
|
||||
fuzz-parser:
|
||||
name: "Fuzz the parser"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.parser == 'true' }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install Python requirements
|
||||
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
with:
|
||||
name: ruff
|
||||
path: ruff-to-test
|
||||
- name: Fuzz
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
|
||||
python scripts/fuzz-parser/fuzz.py 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
|
||||
scripts:
|
||||
name: "test scripts"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -228,9 +273,7 @@ jobs:
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
# Ecosystem check needs linter and/or formatter changes.
|
||||
if: github.event_name == 'pull_request' && ${{
|
||||
needs.determine_changes.outputs.code == 'true'
|
||||
}}
|
||||
if: ${{ github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -509,7 +552,7 @@ jobs:
|
||||
benchmarks:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
path: dist
|
||||
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
*.sha256
|
||||
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -17,4 +17,4 @@ MD013: false
|
||||
# MD024/no-duplicate-heading
|
||||
MD024:
|
||||
# Allow when nested under different parents e.g. CHANGELOG.md
|
||||
allow_different_nesting: true
|
||||
siblings_only: true
|
||||
|
||||
@@ -13,7 +13,7 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.15
|
||||
rev: v0.16
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
@@ -31,7 +31,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.37.0
|
||||
rev: v0.39.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: |
|
||||
@@ -41,7 +41,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.16.22
|
||||
rev: v1.20.9
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -55,7 +55,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.4
|
||||
rev: v0.4.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -70,7 +70,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.3
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
184
CHANGELOG.md
184
CHANGELOG.md
@@ -1,5 +1,187 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-pyi`\] Allow for overloaded `__exit__` and `__aexit__` definitions (`PYI036`) ([#11057](https://github.com/astral-sh/ruff/pull/11057))
|
||||
- \[`pyupgrade`\] Catch usages of `"%s" % var` and provide an unsafe fix (`UP031`) ([#11019](https://github.com/astral-sh/ruff/pull/11019))
|
||||
- \[`refurb`\] Implement new rule that suggests min/max over `sorted()` (`FURB192`) ([#10868](https://github.com/astral-sh/ruff/pull/10868))
|
||||
|
||||
### Server
|
||||
|
||||
- Fix an issue with missing diagnostics for Neovim and Helix ([#11092](https://github.com/astral-sh/ruff/pull/11092))
|
||||
- Implement hover documentation for `noqa` codes ([#11096](https://github.com/astral-sh/ruff/pull/11096))
|
||||
- Introduce common Ruff configuration options with new server settings ([#11062](https://github.com/astral-sh/ruff/pull/11062))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Use `macos-12` for building release wheels to enable macOS 11 compatibility ([#11146](https://github.com/astral-sh/ruff/pull/11146))
|
||||
- \[`flake8-blind-expect`\] Allow raise from in `BLE001` ([#11131](https://github.com/astral-sh/ruff/pull/11131))
|
||||
- \[`flake8-pyi`\] Allow simple assignments to `None` in enum class scopes (`PYI026`) ([#11128](https://github.com/astral-sh/ruff/pull/11128))
|
||||
- \[`flake8-simplify`\] Avoid raising `SIM911` for non-`zip` attribute calls ([#11126](https://github.com/astral-sh/ruff/pull/11126))
|
||||
- \[`refurb`\] Avoid `operator.itemgetter` suggestion for single-item tuple ([#11095](https://github.com/astral-sh/ruff/pull/11095))
|
||||
- \[`ruff`\] Respect per-file-ignores for `RUF100` with no other diagnostics ([#11058](https://github.com/astral-sh/ruff/pull/11058))
|
||||
- \[`ruff`\] Fix async comprehension false positive (`RUF029`) ([#11070](https://github.com/astral-sh/ruff/pull/11070))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-bugbear`\] Document explicitly disabling strict zip (`B905`) ([#11040](https://github.com/astral-sh/ruff/pull/11040))
|
||||
- \[`flake8-type-checking`\] Mention `lint.typing-modules` in `TCH001`, `TCH002`, and `TCH003` ([#11144](https://github.com/astral-sh/ruff/pull/11144))
|
||||
- \[`isort`\] Improve documentation around custom `isort` sections ([#11050](https://github.com/astral-sh/ruff/pull/11050))
|
||||
- \[`pylint`\] Fix documentation oversight for `invalid-X-returns` ([#11094](https://github.com/astral-sh/ruff/pull/11094))
|
||||
|
||||
### Performance
|
||||
|
||||
- Use `matchit` to resolve per-file settings ([#11111](https://github.com/astral-sh/ruff/pull/11111))
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pylint`\] Implement `invalid-hash-returned` (`PLE0309`) ([#10961](https://github.com/astral-sh/ruff/pull/10961))
|
||||
- \[`pylint`\] Implement `invalid-index-returned` (`PLE0305`) ([#10962](https://github.com/astral-sh/ruff/pull/10962))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pylint`\] Allow `NoReturn`-like functions for `__str__`, `__len__`, etc. (`PLE0307`) ([#11017](https://github.com/astral-sh/ruff/pull/11017))
|
||||
- Parser: Use empty range when there's "gap" in token source ([#11032](https://github.com/astral-sh/ruff/pull/11032))
|
||||
- \[`ruff`\] Ignore stub functions in `unused-async` (`RUF029`) ([#11026](https://github.com/astral-sh/ruff/pull/11026))
|
||||
- Parser: Expect indented case block instead of match stmt ([#11033](https://github.com/astral-sh/ruff/pull/11033))
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### A new, hand-written parser
|
||||
|
||||
Ruff's new parser is **>2x faster**, which translates to a **20-40% speedup** for all linting and formatting invocations.
|
||||
There's a lot to say about this exciting change, so check out the [blog post](https://astral.sh/blog/ruff-v0.4.0) for more details!
|
||||
|
||||
See [#10036](https://github.com/astral-sh/ruff/pull/10036) for implementation details.
|
||||
|
||||
### A new language server in Rust
|
||||
|
||||
With this release, we also want to highlight our new language server. `ruff server` is a Rust-powered language
|
||||
server that comes built-in with Ruff. It can be used with any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP).
|
||||
It uses a multi-threaded, lock-free architecture inspired by `rust-analyzer` and it will open the door for a lot
|
||||
of exciting features. It’s also faster than our previous [Python-based language server](https://github.com/astral-sh/ruff-lsp)
|
||||
-- but you probably guessed that already.
|
||||
|
||||
`ruff server` is only in alpha, but it has a lot of features that you can try out today:
|
||||
|
||||
- Lints Python files automatically and shows quick-fixes when available
|
||||
- Formats Python files, with support for range formatting
|
||||
- Comes with commands for quickly performing actions: `ruff.applyAutofix`, `ruff.applyFormat`, and `ruff.applyOrganizeImports`
|
||||
- Supports `source.fixAll` and `source.organizeImports` source actions
|
||||
- Automatically reloads your project configuration when you change it
|
||||
|
||||
To setup `ruff server` with your editor, refer to the [README.md](https://github.com/astral-sh/ruff/blob/main/crates/ruff_server/README.md).
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pycodestyle`\] Do not trigger `E3` rules on `def`s following a function/method with a dummy body ([#10704](https://github.com/astral-sh/ruff/pull/10704))
|
||||
- \[`pylint`\] Implement `invalid-bytes-returned` (`E0308`) ([#10959](https://github.com/astral-sh/ruff/pull/10959))
|
||||
- \[`pylint`\] Implement `invalid-length-returned` (`E0303`) ([#10963](https://github.com/astral-sh/ruff/pull/10963))
|
||||
- \[`pylint`\] Implement `self-cls-assignment` (`W0642`) ([#9267](https://github.com/astral-sh/ruff/pull/9267))
|
||||
- \[`pylint`\] Omit stubs from `invalid-bool` and `invalid-str-return-type` ([#11008](https://github.com/astral-sh/ruff/pull/11008))
|
||||
- \[`ruff`\] New rule `unused-async` (`RUF029`) to detect unneeded `async` keywords on functions ([#9966](https://github.com/astral-sh/ruff/pull/9966))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-bandit`\] Allow `urllib.request.urlopen` calls with static `Request` argument (`S310`) ([#10964](https://github.com/astral-sh/ruff/pull/10964))
|
||||
- \[`flake8-bugbear`\] Treat `raise NotImplemented`-only bodies as stub functions (`B006`) ([#10990](https://github.com/astral-sh/ruff/pull/10990))
|
||||
- \[`flake8-slots`\] Respect same-file `Enum` subclasses (`SLOT000`) ([#11006](https://github.com/astral-sh/ruff/pull/11006))
|
||||
- \[`pylint`\] Support inverted comparisons (`PLR1730`) ([#10920](https://github.com/astral-sh/ruff/pull/10920))
|
||||
|
||||
### Linter
|
||||
|
||||
- Improve handling of builtin symbols in linter rules ([#10919](https://github.com/astral-sh/ruff/pull/10919))
|
||||
- Improve display of rules in `--show-settings` ([#11003](https://github.com/astral-sh/ruff/pull/11003))
|
||||
- Improve inference capabilities of the `BuiltinTypeChecker` ([#10976](https://github.com/astral-sh/ruff/pull/10976))
|
||||
- Resolve classes and functions relative to script name ([#10965](https://github.com/astral-sh/ruff/pull/10965))
|
||||
- Improve performance of `RuleTable::any_enabled` ([#10971](https://github.com/astral-sh/ruff/pull/10971))
|
||||
|
||||
### Server
|
||||
|
||||
*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))
|
||||
- Important errors are now shown as popups ([#10951](https://github.com/astral-sh/ruff/pull/10951))
|
||||
- Introduce settings for directly configuring the linter and formatter ([#10984](https://github.com/astral-sh/ruff/pull/10984))
|
||||
- Resolve configuration for each document individually ([#10950](https://github.com/astral-sh/ruff/pull/10950))
|
||||
- Write a setup guide for Neovim ([#10987](https://github.com/astral-sh/ruff/pull/10987))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Add `RUFF_OUTPUT_FILE` environment variable support ([#10992](https://github.com/astral-sh/ruff/pull/10992))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid `non-augmented-assignment` for reversed, non-commutative operators (`PLR6104`) ([#10909](https://github.com/astral-sh/ruff/pull/10909))
|
||||
- Limit commutative non-augmented-assignments to primitive data types (`PLR6104`) ([#10912](https://github.com/astral-sh/ruff/pull/10912))
|
||||
- Respect `per-file-ignores` for `RUF100` on blanket `# noqa` ([#10908](https://github.com/astral-sh/ruff/pull/10908))
|
||||
- Consider `if` expression for parenthesized with items parsing ([#11010](https://github.com/astral-sh/ruff/pull/11010))
|
||||
- Consider binary expr for parenthesized with items parsing ([#11012](https://github.com/astral-sh/ruff/pull/11012))
|
||||
- Reset `FOR_TARGET` context for all kinds of parentheses ([#11009](https://github.com/astral-sh/ruff/pull/11009))
|
||||
|
||||
## 0.3.7
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Implement `loop-iterator-mutation` (`B909`) ([#9578](https://github.com/astral-sh/ruff/pull/9578))
|
||||
- \[`pylint`\] Implement rule to prefer augmented assignment (`PLR6104`) ([#9932](https://github.com/astral-sh/ruff/pull/9932))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid TOCTOU errors in cache initialization ([#10884](https://github.com/astral-sh/ruff/pull/10884))
|
||||
- \[`pylint`\] Recode `nan-comparison` rule to `W0177` ([#10894](https://github.com/astral-sh/ruff/pull/10894))
|
||||
- \[`pylint`\] Reverse min-max logic in `if-stmt-min-max` ([#10890](https://github.com/astral-sh/ruff/pull/10890))
|
||||
|
||||
## 0.3.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pylint`\] Implement `bad-staticmethod-argument` (`PLW0211`) ([#10781](https://github.com/astral-sh/ruff/pull/10781))
|
||||
- \[`pylint`\] Implement `if-stmt-min-max` (`PLR1730`, `PLR1731`) ([#10002](https://github.com/astral-sh/ruff/pull/10002))
|
||||
- \[`pyupgrade`\] Replace `str,Enum` multiple inheritance with `StrEnum` `UP042` ([#10713](https://github.com/astral-sh/ruff/pull/10713))
|
||||
- \[`refurb`\] Implement `if-expr-instead-of-or-operator` (`FURB110`) ([#10687](https://github.com/astral-sh/ruff/pull/10687))
|
||||
- \[`refurb`\] Implement `int-on-sliced-str` (`FURB166`) ([#10650](https://github.com/astral-sh/ruff/pull/10650))
|
||||
- \[`refurb`\] Implement `write-whole-file` (`FURB103`) ([#10802](https://github.com/astral-sh/ruff/pull/10802))
|
||||
- \[`refurb`\] Support `itemgetter` in `reimplemented-operator` (`FURB118`) ([#10526](https://github.com/astral-sh/ruff/pull/10526))
|
||||
- \[`flake8_comprehensions`\] Add `sum`/`min`/`max` to unnecessary comprehension check (`C419`) ([#10759](https://github.com/astral-sh/ruff/pull/10759))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pydocstyle`\] Require capitalizing docstrings where the first sentence is a single word (`D403`) ([#10776](https://github.com/astral-sh/ruff/pull/10776))
|
||||
- \[`pycodestyle`\] Ignore annotated lambdas in class scopes (`E731`) ([#10720](https://github.com/astral-sh/ruff/pull/10720))
|
||||
- \[`flake8-pyi`\] Various improvements to PYI034 ([#10807](https://github.com/astral-sh/ruff/pull/10807))
|
||||
- \[`flake8-slots`\] Flag subclasses of call-based `typing.NamedTuple`s as well as subclasses of `collections.namedtuple()` (`SLOT002`) ([#10808](https://github.com/astral-sh/ruff/pull/10808))
|
||||
- \[`pyflakes`\] Allow forward references in class bases in stub files (`F821`) ([#10779](https://github.com/astral-sh/ruff/pull/10779))
|
||||
- \[`pygrep-hooks`\] Improve `blanket-noqa` error message (`PGH004`) ([#10851](https://github.com/astral-sh/ruff/pull/10851))
|
||||
|
||||
### CLI
|
||||
|
||||
- Support `FORCE_COLOR` env var ([#10839](https://github.com/astral-sh/ruff/pull/10839))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Support negated patterns in `[extend-]per-file-ignores` ([#10852](https://github.com/astral-sh/ruff/pull/10852))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-import-conventions`\] Accept non-aliased (but correct) import in `unconventional-import-alias` (`ICN001`) ([#10729](https://github.com/astral-sh/ruff/pull/10729))
|
||||
- \[`flake8-quotes`\] Add semantic model flag when inside f-string replacement field ([#10766](https://github.com/astral-sh/ruff/pull/10766))
|
||||
- \[`pep8-naming`\] Recursively resolve `TypeDicts` for N815 violations ([#10719](https://github.com/astral-sh/ruff/pull/10719))
|
||||
- \[`flake8-quotes`\] Respect `Q00*` ignores in `flake8-quotes` rules ([#10728](https://github.com/astral-sh/ruff/pull/10728))
|
||||
- \[`flake8-simplify`\] Show negated condition in `needless-bool` diagnostics (`SIM103`) ([#10854](https://github.com/astral-sh/ruff/pull/10854))
|
||||
- \[`ruff`\] Use within-scope shadowed bindings in `asyncio-dangling-task` (`RUF006`) ([#10793](https://github.com/astral-sh/ruff/pull/10793))
|
||||
- \[`flake8-pytest-style`\] Fix single-tuple conversion in `pytest-parametrize-values-wrong-type` (`PT007`) ([#10862](https://github.com/astral-sh/ruff/pull/10862))
|
||||
- \[`flake8-return`\] Ignore assignments to annotated variables in `unnecessary-assign` (`RET504`) ([#10741](https://github.com/astral-sh/ruff/pull/10741))
|
||||
- \[`refurb`\] Do not allow any keyword arguments for `read-whole-file` in `rb` mode (`FURB101`) ([#10803](https://github.com/astral-sh/ruff/pull/10803))
|
||||
- \[`pylint`\] Don't recommend decorating staticmethods with `@singledispatch` (`PLE1519`, `PLE1520`) ([#10637](https://github.com/astral-sh/ruff/pull/10637))
|
||||
- \[`pydocstyle`\] Use section name range for all section-related docstring diagnostics ([#10740](https://github.com/astral-sh/ruff/pull/10740))
|
||||
- Respect `# noqa` directives on `__all__` openers ([#10798](https://github.com/astral-sh/ruff/pull/10798))
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Preview features
|
||||
@@ -1327,7 +1509,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
|
||||
|
||||
|
||||
426
Cargo.lock
generated
426
Cargo.lock
generated
@@ -123,15 +123,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
version = "0.1.6"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6"
|
||||
checksum = "b7c5c8e418080ef8aa932039d12eda7b6f5043baf48f1523c166fbc32d004534"
|
||||
dependencies = [
|
||||
"fs-err",
|
||||
"os_str_bytes",
|
||||
@@ -143,15 +143,6 @@ version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
|
||||
dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -173,21 +164,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -244,6 +220,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chic"
|
||||
version = "1.2.2"
|
||||
@@ -255,9 +237,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -365,7 +347,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -376,9 +358,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
version = "2.0.1"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994"
|
||||
checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"terminfo",
|
||||
@@ -389,9 +371,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b85b056aa0541d1975ebc524149dde72803a5d7352b6aebf9eabc44f9017246"
|
||||
checksum = "735f16ee0fc63cb90596cd7b57ce481522adfe1714f95bc04a94d4f4b0a06a6d"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"libc",
|
||||
@@ -400,9 +382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02ae9de916d6315a5129bca2fc7957285f0b9f77a2f6a8734a0a146caee2b0b6"
|
||||
checksum = "572ca9c8ad460591b40aad63c99d6746aa3c532f979175344eb015389499860c"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"colored",
|
||||
@@ -513,19 +495,6 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.12"
|
||||
@@ -554,15 +523,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
@@ -596,7 +556,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -607,7 +567,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -634,16 +594,6 @@ dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
@@ -667,17 +617,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "drop_bomb"
|
||||
version = "0.1.5"
|
||||
@@ -696,15 +635,6 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
@@ -777,12 +707,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
@@ -1005,9 +929,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.5"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
@@ -1072,9 +996,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta-cmd"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1980f17994b79f75670aa90cfc8d35edc4aa248f16aa48b5e27835b080e452a2"
|
||||
checksum = "ffeeefa927925cced49ccb01bf3e57c9d4cd132df21e576eb9415baeab2d3de6"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"serde",
|
||||
@@ -1108,7 +1032,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1141,15 +1065,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@@ -1200,33 +1115,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"bit-set",
|
||||
"ena",
|
||||
"itertools 0.11.0",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
"regex-syntax 0.8.2",
|
||||
"string_cache",
|
||||
"term",
|
||||
"tiny-keccak",
|
||||
"unicode-xid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -1271,9 +1159,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890ee958b936e712c6f1c184f208176973e73c2e4f8d3cf499f94eb112f647f9"
|
||||
checksum = "6f1e25d1b119ab5c2f15a6e081bb94a8d547c5c2ad065f5fd0dbb683f31ced91"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"libcst_derive",
|
||||
@@ -1286,12 +1174,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbd2f3cd9346422ebdc3a614aed6969d4e0b3e9c10517f33b30326acf894c11"
|
||||
checksum = "4a5011f2d59093de14a4a90e01b9d85dee9276e58a25f0107dcee167dd601be0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1327,16 +1215,6 @@ version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
@@ -1383,6 +1261,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357db4d45704af452edb5861033c1c28db6f583d2e34cc6d40c6e096eb111499"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
@@ -1432,25 +1316,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
name = "newtype-uuid"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nextest-workspace-hack"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
|
||||
checksum = "3526cb7c660872e401beaf3297f95f548ce3b4b4bdd8121b7c0713771d7c4a6e"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1560,29 +1442,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
@@ -1607,6 +1466,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
@@ -1654,9 +1519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15efd4d885c29126cc93e12af3087896e2518bd5ca0fb328c19c4ef9cecfa8be"
|
||||
checksum = "ca0a570e7ec9171250cac57614e901f62408094b54b3798bb920d3cf0d4a0e09"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
@@ -1686,23 +1551,13 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1712,7 +1567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared 0.11.2",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1721,19 +1576,10 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
@@ -1757,7 +1603,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1772,12 +1618,6 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
@@ -1790,9 +1630,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1812,13 +1652,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9599bffc2cd7511355996e0cfd979266b2cfa3f3ff5247d07a3a6e1ded6158"
|
||||
checksum = "cfc1a6a5406a114913df2df8507998c755311b55b78584bed5f6e88f6417c4d4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap",
|
||||
"nextest-workspace-hack",
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
@@ -1836,9 +1676,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1975,7 +1815,7 @@ dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1995,7 +1835,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2157,7 +1997,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2181,7 +2021,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs 0.5.0",
|
||||
"pep440_rs 0.6.0",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
@@ -2225,7 +2065,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2336,22 +2176,23 @@ dependencies = [
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
"bitflags 2.5.0",
|
||||
"bstr",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"memchr",
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"static_assertions",
|
||||
"tiny-keccak",
|
||||
"unicode-ident",
|
||||
"unicode-normalization",
|
||||
"unicode_names2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2404,12 +2245,13 @@ name = "ruff_server"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"crossbeam-channel",
|
||||
"insta",
|
||||
"jod-thread",
|
||||
"libc",
|
||||
"lsp-server",
|
||||
"lsp-types",
|
||||
"regex",
|
||||
"ruff_diagnostics",
|
||||
"ruff_formatter",
|
||||
"ruff_linter",
|
||||
@@ -2425,11 +2267,12 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2501,8 +2344,10 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"matchit",
|
||||
"path-absolutize",
|
||||
"pep440_rs 0.5.0",
|
||||
"path-slash",
|
||||
"pep440_rs 0.6.0",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
@@ -2631,12 +2476,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
@@ -2645,9 +2484,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2665,13 +2504,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2687,9 +2526,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.115"
|
||||
version = "1.0.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2704,7 +2543,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2745,7 +2584,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2802,19 +2641,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.0"
|
||||
@@ -2855,7 +2681,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2877,9 +2703,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.57"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2898,17 +2724,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"rustversion",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
@@ -2950,7 +2765,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2961,28 +2776,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3015,15 +2830,6 @@ dependencies = [
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
@@ -3103,7 +2909,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3244,12 +3050,6 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "1.2.2"
|
||||
@@ -3339,7 +3139,7 @@ checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3424,7 +3224,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3458,7 +3258,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3491,7 +3291,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3515,14 +3315,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3715,6 +3515,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
@@ -3747,7 +3553,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -15,7 +15,7 @@ license = "MIT"
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
argfile = { version = "0.2.0" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bstr = { version = "1.9.1" }
|
||||
@@ -23,14 +23,14 @@ cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
clearscreen = { version = "3.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
crossbeam-channel = { version = "0.5.12" }
|
||||
dirs = { version = "5.0.0" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
@@ -46,19 +46,19 @@ imperative = { version = "1.0.4" }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.5.0" }
|
||||
insta-cmd = { version = "0.6.0" }
|
||||
is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
itertools = { version = "0.12.1" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
jod-thread = { version = "0.1.2" }
|
||||
lalrpop-util = { version = "0.20.0", default-features = false }
|
||||
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||
libc = { version = "0.2.153" }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
lsp-server = { version = "0.7.6" }
|
||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
||||
matchit = { version = "0.8.1" }
|
||||
memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
@@ -66,12 +66,13 @@ notify = { version = "6.1.1" }
|
||||
num_cpus = { version = "1.16.0" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.5.0", features = ["serde"] }
|
||||
pep440_rs = { version = "0.6.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
quick-junit = { version = "0.4.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
rayon = { version = "1.10.0" }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](LICENSE)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/astral-sh/ruff/actions)
|
||||
[](https://discord.com/invite/astral-sh)
|
||||
@@ -50,6 +50,7 @@ times faster than any individual tool.
|
||||
Ruff is extremely actively developed and used in major open-source projects like:
|
||||
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
- [Apache Superset](https://github.com/apache/superset)
|
||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||
- [Hugging Face](https://github.com/huggingface/transformers)
|
||||
- [Pandas](https://github.com/pandas-dev/pandas)
|
||||
@@ -151,7 +152,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.3.5
|
||||
rev: v0.4.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -498,7 +499,7 @@ If you're using Ruff, consider adding the Ruff badge to your project's `README.m
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
This repository is licensed under the [MIT License](LICENSE)
|
||||
|
||||
<div align="center">
|
||||
<a target="_blank" href="https://astral.sh" style="background:none">
|
||||
|
||||
@@ -3,9 +3,17 @@
|
||||
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
|
||||
|
||||
[default.extend-words]
|
||||
"arange" = "arange" # e.g. `numpy.arange`
|
||||
hel = "hel"
|
||||
whos = "whos"
|
||||
spawnve = "spawnve"
|
||||
ned = "ned"
|
||||
pn = "pn" # `import panel as pd` is a thing
|
||||
poit = "poit"
|
||||
BA = "BA" # acronym for "Bad Allowed", used in testing.
|
||||
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# Line ignore with trailing "spellchecker:disable-line"
|
||||
"(?Rm)^.*#\\s*spellchecker:disable-line$"
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -190,7 +190,7 @@ pub struct CheckCommand {
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
|
||||
/// Specify file to write the linter output to (default: stdout).
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, env = "RUFF_OUTPUT_FILE")]
|
||||
pub output_file: Option<PathBuf>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, value_enum)]
|
||||
|
||||
@@ -375,15 +375,17 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
fs::create_dir_all(path.join(VERSION))?;
|
||||
|
||||
// Add the CACHEDIR.TAG.
|
||||
if !cachedir::is_tagged(path)? {
|
||||
cachedir::add_tag(path)?;
|
||||
}
|
||||
cachedir::ensure_tag(path)?;
|
||||
|
||||
// Add the .gitignore.
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
let mut file = fs::File::create(gitignore_path)?;
|
||||
file.write_all(b"# Automatically created by ruff.\n*\n")?;
|
||||
match fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path.join(".gitignore"))
|
||||
{
|
||||
Ok(mut file) => file.write_all(b"# Automatically created by ruff.\n*\n")?,
|
||||
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -149,6 +149,13 @@ pub fn run(
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
// support FORCE_COLOR env var
|
||||
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
|
||||
if force_color.len() > 0 {
|
||||
colored::control::set_override(true);
|
||||
}
|
||||
}
|
||||
|
||||
set_up_logging(global_options.log_level())?;
|
||||
|
||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||
|
||||
@@ -523,7 +523,7 @@ from module import =
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse main.py:2:20: Unexpected token '='
|
||||
error: Failed to parse main.py:2:20: Expected an import name
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -731,11 +731,11 @@ fn stdin_parse_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:17: E999 SyntaxError: Unexpected token '='
|
||||
-:1:17: E999 SyntaxError: Expected an import name
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse at 1:17: Unexpected token '='
|
||||
error: Failed to parse at 1:17: Expected an import name
|
||||
"###);
|
||||
}
|
||||
|
||||
|
||||
@@ -1168,3 +1168,119 @@ def func():
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Per-file selects via ! negation in per-file-ignores
|
||||
#[test]
|
||||
fn negated_per_file_ignores() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"!selected.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let selected = tempdir.path().join("selected.py");
|
||||
fs::write(selected, "")?;
|
||||
let ignored = tempdir.path().join("ignored.py");
|
||||
fs::write(ignored, "")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negated_per_file_ignores_absolute() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"!src/**.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let src_dir = tempdir.path().join("src");
|
||||
fs::create_dir(&src_dir)?;
|
||||
let selected = src_dir.join("selected.py");
|
||||
fs::write(selected, "")?;
|
||||
let ignored = tempdir.path().join("ignored.py");
|
||||
fs::write(ignored, "")?;
|
||||
|
||||
insta::with_settings!({filters => vec![
|
||||
// Replace windows paths
|
||||
(r"\\", "/"),
|
||||
]}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
src/selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// patterns are additive, can't use negative patterns to "un-ignore"
|
||||
#[test]
|
||||
fn negated_per_file_ignores_overlap() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"*.py" = ["RUF"]
|
||||
"!foo.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let foo_file = tempdir.path().join("foo.py");
|
||||
fs::write(foo_file, "")?;
|
||||
let bar_file = tempdir.path().join("bar.py");
|
||||
fs::write(bar_file, "")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,8 +50,10 @@ file_resolver.exclude = [
|
||||
"venv",
|
||||
]
|
||||
file_resolver.extend_exclude = [
|
||||
"crates/ruff/resources/",
|
||||
"crates/ruff_linter/resources/",
|
||||
"crates/ruff_python_formatter/resources/",
|
||||
"crates/ruff_python_parser/resources/",
|
||||
]
|
||||
file_resolver.force_exclude = false
|
||||
file_resolver.include = [
|
||||
@@ -67,128 +69,128 @@ file_resolver.project_root = "[BASEPATH]"
|
||||
linter.exclude = []
|
||||
linter.project_root = "[BASEPATH]"
|
||||
linter.rules.enabled = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
multiple-imports-on-one-line (E401),
|
||||
module-import-not-at-top-of-file (E402),
|
||||
multiple-statements-on-one-line-colon (E701),
|
||||
multiple-statements-on-one-line-semicolon (E702),
|
||||
useless-semicolon (E703),
|
||||
none-comparison (E711),
|
||||
true-false-comparison (E712),
|
||||
not-in-test (E713),
|
||||
not-is-test (E714),
|
||||
type-comparison (E721),
|
||||
bare-except (E722),
|
||||
lambda-assignment (E731),
|
||||
ambiguous-variable-name (E741),
|
||||
ambiguous-class-name (E742),
|
||||
ambiguous-function-name (E743),
|
||||
io-error (E902),
|
||||
syntax-error (E999),
|
||||
unused-import (F401),
|
||||
import-shadowed-by-loop-var (F402),
|
||||
undefined-local-with-import-star (F403),
|
||||
late-future-import (F404),
|
||||
undefined-local-with-import-star-usage (F405),
|
||||
undefined-local-with-nested-import-star-usage (F406),
|
||||
future-feature-not-defined (F407),
|
||||
percent-format-invalid-format (F501),
|
||||
percent-format-expected-mapping (F502),
|
||||
percent-format-expected-sequence (F503),
|
||||
percent-format-extra-named-arguments (F504),
|
||||
percent-format-missing-argument (F505),
|
||||
percent-format-mixed-positional-and-named (F506),
|
||||
percent-format-positional-count-mismatch (F507),
|
||||
percent-format-star-requires-sequence (F508),
|
||||
percent-format-unsupported-format-character (F509),
|
||||
string-dot-format-invalid-format (F521),
|
||||
string-dot-format-extra-named-arguments (F522),
|
||||
string-dot-format-extra-positional-arguments (F523),
|
||||
string-dot-format-missing-arguments (F524),
|
||||
string-dot-format-mixing-automatic (F525),
|
||||
f-string-missing-placeholders (F541),
|
||||
multi-value-repeated-key-literal (F601),
|
||||
multi-value-repeated-key-variable (F602),
|
||||
expressions-in-star-assignment (F621),
|
||||
multiple-starred-expressions (F622),
|
||||
assert-tuple (F631),
|
||||
is-literal (F632),
|
||||
invalid-print-syntax (F633),
|
||||
if-tuple (F634),
|
||||
break-outside-loop (F701),
|
||||
continue-outside-loop (F702),
|
||||
yield-outside-function (F704),
|
||||
return-outside-function (F706),
|
||||
default-except-not-last (F707),
|
||||
forward-annotation-syntax-error (F722),
|
||||
redefined-while-unused (F811),
|
||||
undefined-name (F821),
|
||||
undefined-export (F822),
|
||||
undefined-local (F823),
|
||||
unused-variable (F841),
|
||||
unused-annotation (F842),
|
||||
raise-not-implemented (F901),
|
||||
]
|
||||
linter.rules.should_fix = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
multiple-imports-on-one-line (E401),
|
||||
module-import-not-at-top-of-file (E402),
|
||||
multiple-statements-on-one-line-colon (E701),
|
||||
multiple-statements-on-one-line-semicolon (E702),
|
||||
useless-semicolon (E703),
|
||||
none-comparison (E711),
|
||||
true-false-comparison (E712),
|
||||
not-in-test (E713),
|
||||
not-is-test (E714),
|
||||
type-comparison (E721),
|
||||
bare-except (E722),
|
||||
lambda-assignment (E731),
|
||||
ambiguous-variable-name (E741),
|
||||
ambiguous-class-name (E742),
|
||||
ambiguous-function-name (E743),
|
||||
io-error (E902),
|
||||
syntax-error (E999),
|
||||
unused-import (F401),
|
||||
import-shadowed-by-loop-var (F402),
|
||||
undefined-local-with-import-star (F403),
|
||||
late-future-import (F404),
|
||||
undefined-local-with-import-star-usage (F405),
|
||||
undefined-local-with-nested-import-star-usage (F406),
|
||||
future-feature-not-defined (F407),
|
||||
percent-format-invalid-format (F501),
|
||||
percent-format-expected-mapping (F502),
|
||||
percent-format-expected-sequence (F503),
|
||||
percent-format-extra-named-arguments (F504),
|
||||
percent-format-missing-argument (F505),
|
||||
percent-format-mixed-positional-and-named (F506),
|
||||
percent-format-positional-count-mismatch (F507),
|
||||
percent-format-star-requires-sequence (F508),
|
||||
percent-format-unsupported-format-character (F509),
|
||||
string-dot-format-invalid-format (F521),
|
||||
string-dot-format-extra-named-arguments (F522),
|
||||
string-dot-format-extra-positional-arguments (F523),
|
||||
string-dot-format-missing-arguments (F524),
|
||||
string-dot-format-mixing-automatic (F525),
|
||||
f-string-missing-placeholders (F541),
|
||||
multi-value-repeated-key-literal (F601),
|
||||
multi-value-repeated-key-variable (F602),
|
||||
expressions-in-star-assignment (F621),
|
||||
multiple-starred-expressions (F622),
|
||||
assert-tuple (F631),
|
||||
is-literal (F632),
|
||||
invalid-print-syntax (F633),
|
||||
if-tuple (F634),
|
||||
break-outside-loop (F701),
|
||||
continue-outside-loop (F702),
|
||||
yield-outside-function (F704),
|
||||
return-outside-function (F706),
|
||||
default-except-not-last (F707),
|
||||
forward-annotation-syntax-error (F722),
|
||||
redefined-while-unused (F811),
|
||||
undefined-name (F821),
|
||||
undefined-export (F822),
|
||||
undefined-local (F823),
|
||||
unused-variable (F841),
|
||||
unused-annotation (F842),
|
||||
raise-not-implemented (F901),
|
||||
]
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
|
||||
@@ -65,7 +65,7 @@ use seahash::SeaHasher;
|
||||
/// The main reason is that hashes and cache keys have different constraints:
|
||||
///
|
||||
/// * Cache keys are less performance sensitive: Hashes must be super fast to compute for performant hashed-collections. That's
|
||||
/// why some standard types don't implement [`Hash`] where it would be safe to to implement [`CacheKey`], e.g. `HashSet`
|
||||
/// why some standard types don't implement [`Hash`] where it would be safe to implement [`CacheKey`], e.g. `HashSet`
|
||||
/// * Cache keys must be deterministic where hash keys do not have this constraint. That's why pointers don't implement [`CacheKey`] but they implement [`Hash`].
|
||||
/// * Ideally, cache keys are portable
|
||||
///
|
||||
|
||||
@@ -71,6 +71,14 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_parent(mut self, parent: TextSize) -> Self {
|
||||
self.set_parent(parent);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the location of the diagnostic's parent node.
|
||||
#[inline]
|
||||
pub fn set_parent(&mut self, parent: TextSize) {
|
||||
|
||||
@@ -138,7 +138,7 @@ pub const fn empty_line() -> Line {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a a single line:
|
||||
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a single line:
|
||||
/// ```
|
||||
/// use ruff_formatter::{format, format_args};
|
||||
/// use ruff_formatter::prelude::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -17,3 +17,9 @@ urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||
urllib.request.URLopener().open('http://www.google.com')
|
||||
urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||
urllib.request.URLopener().open(url)
|
||||
|
||||
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
|
||||
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
|
||||
urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
||||
urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
||||
urllib.request.urlopen(urllib.request.Request(url))
|
||||
|
||||
@@ -124,3 +124,8 @@ try:
|
||||
pass
|
||||
except Exception:
|
||||
error("...", exc_info=True)
|
||||
|
||||
try:
|
||||
...
|
||||
except Exception as e:
|
||||
raise ValueError from e
|
||||
|
||||
@@ -6,6 +6,25 @@ def this_is_a_bug():
|
||||
print("Ooh, callable! Or is it?")
|
||||
|
||||
|
||||
def still_a_bug():
|
||||
import builtins
|
||||
o = object()
|
||||
if builtins.hasattr(o, "__call__"):
|
||||
print("B U G")
|
||||
if builtins.getattr(o, "__call__", False):
|
||||
print("B U G")
|
||||
|
||||
|
||||
def trickier_fix_for_this_one():
|
||||
o = object()
|
||||
|
||||
def callable(x):
|
||||
return True
|
||||
|
||||
if hasattr(o, "__call__"):
|
||||
print("STILL a bug!")
|
||||
|
||||
|
||||
def this_is_fine():
|
||||
o = object()
|
||||
if callable(o):
|
||||
|
||||
20
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_8.py
vendored
Normal file
20
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_8.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
def foo(a: list = []):
|
||||
raise NotImplementedError("")
|
||||
|
||||
|
||||
def bar(a: dict = {}):
|
||||
""" This one also has a docstring"""
|
||||
raise NotImplementedError("and has some text in here")
|
||||
|
||||
|
||||
def baz(a: list = []):
|
||||
"""This one raises a different exception"""
|
||||
raise IndexError()
|
||||
|
||||
|
||||
def qux(a: list = []):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def quux(a: list = []):
|
||||
raise NotImplemented
|
||||
@@ -23,3 +23,15 @@ def okay(data: custom.ImmutableTypeA = foo()):
|
||||
|
||||
def error_due_to_missing_import(data: List[str] = Depends(None)):
|
||||
...
|
||||
|
||||
|
||||
class Class:
|
||||
pass
|
||||
|
||||
|
||||
def okay(obj=Class()):
|
||||
...
|
||||
|
||||
|
||||
def error(obj=OtherClass()):
|
||||
...
|
||||
|
||||
@@ -64,3 +64,6 @@ setattr(*foo, "bar", None)
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901
|
||||
getattr(self.
|
||||
registration.registry, '__name__')
|
||||
|
||||
import builtins
|
||||
builtins.getattr(foo, "bar")
|
||||
|
||||
@@ -23,3 +23,7 @@ zip([1, 2, 3], repeat(1, times=None))
|
||||
# Errors (limited iterators).
|
||||
zip([1, 2, 3], repeat(1, 1))
|
||||
zip([1, 2, 3], repeat(1, times=4))
|
||||
|
||||
import builtins
|
||||
# Still an error even though it uses the qualified name
|
||||
builtins.zip([1, 2, 3])
|
||||
|
||||
160
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py
vendored
Normal file
160
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Should emit:
|
||||
B909 - on lines 11, 25, 26, 40, 46
|
||||
"""
|
||||
|
||||
# lists
|
||||
|
||||
some_list = [1, 2, 3]
|
||||
some_other_list = [1, 2, 3]
|
||||
for elem in some_list:
|
||||
# errors
|
||||
some_list.remove(0)
|
||||
del some_list[2]
|
||||
some_list.append(elem)
|
||||
some_list.sort()
|
||||
some_list.reverse()
|
||||
some_list.clear()
|
||||
some_list.extend([1, 2])
|
||||
some_list.insert(1, 1)
|
||||
some_list.pop(1)
|
||||
some_list.pop()
|
||||
|
||||
# conditional break should error
|
||||
if elem == 2:
|
||||
some_list.remove(0)
|
||||
if elem == 3:
|
||||
break
|
||||
|
||||
# non-errors
|
||||
some_other_list.remove(elem)
|
||||
del some_list
|
||||
del some_other_list
|
||||
found_idx = some_list.index(elem)
|
||||
some_list = 3
|
||||
|
||||
# unconditional break should not error
|
||||
if elem == 2:
|
||||
some_list.remove(elem)
|
||||
break
|
||||
|
||||
|
||||
# dicts
|
||||
mydicts = {"a": {"foo": 1, "bar": 2}}
|
||||
|
||||
for elem in mydicts:
|
||||
# errors
|
||||
mydicts.popitem()
|
||||
mydicts.setdefault("foo", 1)
|
||||
mydicts.update({"foo": "bar"})
|
||||
|
||||
# no errors
|
||||
elem.popitem()
|
||||
elem.setdefault("foo", 1)
|
||||
elem.update({"foo": "bar"})
|
||||
|
||||
# sets
|
||||
|
||||
myset = {1, 2, 3}
|
||||
|
||||
for _ in myset:
|
||||
# errors
|
||||
myset.update({4, 5})
|
||||
myset.intersection_update({4, 5})
|
||||
myset.difference_update({4, 5})
|
||||
myset.symmetric_difference_update({4, 5})
|
||||
myset.add(4)
|
||||
myset.discard(3)
|
||||
|
||||
# no errors
|
||||
del myset
|
||||
|
||||
|
||||
# members
|
||||
class A:
|
||||
some_list: list
|
||||
|
||||
def __init__(self, ls):
|
||||
self.some_list = list(ls)
|
||||
|
||||
|
||||
a = A((1, 2, 3))
|
||||
# ensure member accesses are handled as errors
|
||||
for elem in a.some_list:
|
||||
a.some_list.remove(0)
|
||||
del a.some_list[2]
|
||||
|
||||
|
||||
# Augassign should error
|
||||
|
||||
foo = [1, 2, 3]
|
||||
bar = [4, 5, 6]
|
||||
for _ in foo:
|
||||
foo *= 2
|
||||
foo += bar
|
||||
foo[1] = 9
|
||||
foo[1:2] = bar
|
||||
foo[1:2:3] = bar
|
||||
|
||||
foo = {1, 2, 3}
|
||||
bar = {4, 5, 6}
|
||||
for _ in foo: # should error
|
||||
foo |= bar
|
||||
foo &= bar
|
||||
foo -= bar
|
||||
foo ^= bar
|
||||
|
||||
|
||||
# more tests for unconditional breaks - should not error
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
for _ in bar:
|
||||
bar.remove(1)
|
||||
break
|
||||
break
|
||||
|
||||
# should not error
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
for _ in bar:
|
||||
...
|
||||
break
|
||||
|
||||
# should error (?)
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
if bar:
|
||||
bar.remove(1)
|
||||
break
|
||||
break
|
||||
|
||||
# should error
|
||||
for _ in foo:
|
||||
if bar:
|
||||
pass
|
||||
else:
|
||||
foo.remove(1)
|
||||
|
||||
# should error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
pass
|
||||
|
||||
# should not error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
break
|
||||
|
||||
# should error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
# should not error
|
||||
for elem in some_list:
|
||||
del some_list[elem]
|
||||
some_list[elem] = 1
|
||||
some_list.remove(elem)
|
||||
some_list.discard(elem)
|
||||
@@ -1,6 +1,9 @@
|
||||
# PIE808
|
||||
range(0, 10)
|
||||
|
||||
import builtins
|
||||
builtins.range(0, 10)
|
||||
|
||||
# OK
|
||||
range(x, 10)
|
||||
range(-15, 10)
|
||||
|
||||
@@ -14,6 +14,15 @@ IntOrStr: TypeAlias = int | str
|
||||
IntOrFloat: Foo = int | float
|
||||
AliasNone: typing.TypeAlias = None
|
||||
|
||||
# these are ok
|
||||
class NotAnEnum:
|
||||
NOT_A_STUB_SO_THIS_IS_FINE = None
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class FooEnum(Enum): ...
|
||||
|
||||
class BarEnum(FooEnum):
|
||||
BAR = None
|
||||
|
||||
VarAlias = str
|
||||
AliasFoo = Foo
|
||||
|
||||
@@ -13,6 +13,16 @@ IntOrStr: TypeAlias = int | str
|
||||
IntOrFloat: Foo = int | float
|
||||
AliasNone: typing.TypeAlias = None
|
||||
|
||||
class NotAnEnum:
|
||||
FLAG_THIS = None
|
||||
|
||||
# these are ok
|
||||
from enum import Enum
|
||||
|
||||
class FooEnum(Enum): ...
|
||||
|
||||
class BarEnum(FooEnum):
|
||||
BAR = None
|
||||
|
||||
VarAlias = str
|
||||
AliasFoo = Foo
|
||||
|
||||
@@ -195,6 +195,13 @@ class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
def __aiter__(self) -> typing.AsyncIterator[str]:
|
||||
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
|
||||
class SubclassOfBadIterator3(BadIterator3):
|
||||
def __iter__(self) -> Iterator[int]: # Y034
|
||||
...
|
||||
|
||||
class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
|
||||
...
|
||||
|
||||
class AsyncIteratorReturningAsyncIterable:
|
||||
def __aiter__(self) -> AsyncIterable[str]:
|
||||
@@ -225,6 +232,11 @@ class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
|
||||
class SubclassOfMetaclassInWhichSelfCannotBeUsed(MetaclassInWhichSelfCannotBeUsed4):
|
||||
def __new__(cls) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __enter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
async def __aenter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __isub__(self, other: SubclassOfMetaclassInWhichSelfCannotBeUsed) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
|
||||
@@ -3,7 +3,7 @@ import types
|
||||
import typing
|
||||
from collections.abc import Awaitable
|
||||
from types import TracebackType
|
||||
from typing import Any, Type
|
||||
from typing import Any, Type, overload
|
||||
|
||||
import _typeshed
|
||||
import typing_extensions
|
||||
@@ -73,3 +73,97 @@ class BadFive:
|
||||
class BadSix:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
|
||||
|
||||
class AllPositionalOnlyArgs:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
|
||||
class BadAllPositionalOnlyArgs:
|
||||
def __exit__(self, typ: type[Exception] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType, /) -> None: ...
|
||||
|
||||
# Definitions not in a class scope can do whatever, we don't care
|
||||
def __exit__(self, *args: bool) -> None: ...
|
||||
async def __aexit__(self, *, go_crazy: bytes) -> list[str]: ...
|
||||
|
||||
# Here come the overloads...
|
||||
|
||||
class AcceptableOverload1:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, exc_tb: None) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[BaseException], exc: BaseException, exc_tb: TracebackType) -> None: ...
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None) -> None: ...
|
||||
|
||||
# Using `object` or `Unused` in an overload definition is kinda strange,
|
||||
# but let's allow it to be on the safe side
|
||||
class AcceptableOverload2:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, exc_tb: object) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: Unused, exc: BaseException, exc_tb: object) -> None: ...
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None) -> None: ...
|
||||
|
||||
class AcceptableOverload3:
|
||||
# Just ignore any overloads that don't have exactly 3 annotated non-self parameters.
|
||||
# We don't have the ability (yet) to do arbitrary checking
|
||||
# of whether one function definition is a subtype of another...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, exc_tb: bool, weird_extra_arg: bool) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: bool, /, exc: bool, exc_tb: bool, *, keyword_only: str) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, *args: object) -> None: ...
|
||||
async def __aexit__(self, *args: object) -> None: ...
|
||||
|
||||
class AcceptableOverload4:
|
||||
# Same as above
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[Exception], exc: type[Exception], exc_tb: types.TracebackType) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: type[Exception], exc: type[Exception], exc_tb: types.TracebackType, *, extra: str = "foo") -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: None, exc: None, tb: None) -> None: ...
|
||||
async def __aexit__(self, *args: object) -> None: ...
|
||||
|
||||
class StrangeNumberOfOverloads:
|
||||
# Only one overload? Type checkers will emit an error, but we should just ignore it
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
# More than two overloads? Anything could be going on; again, just ignore all the overloads
|
||||
@overload
|
||||
async def __aexit__(self, arg: bool) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, arg: None, arg2: None, arg3: None) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, arg: bool, arg2: bool, arg3: bool) -> None: ...
|
||||
async def __aexit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
|
||||
# TODO: maybe we should emit an error on this one as well?
|
||||
class BizarreAsyncSyncOverloadMismatch:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
@overload
|
||||
async def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
|
||||
class UnacceptableOverload1:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, tb: None) -> None: ... # Okay
|
||||
@overload
|
||||
def __exit__(self, exc_typ: Exception, exc: Exception, tb: TracebackType) -> None: ... # PYI036
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
|
||||
class UnacceptableOverload2:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: None, tb: None) -> None: ... # PYI036
|
||||
@overload
|
||||
def __exit__(self, exc_typ: object, exc: Exception, tb: builtins.TracebackType) -> None: ... # PYI036
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
|
||||
@@ -3,7 +3,7 @@ import types
|
||||
import typing
|
||||
from collections.abc import Awaitable
|
||||
from types import TracebackType
|
||||
from typing import Any, Type
|
||||
from typing import Any, Type, overload
|
||||
|
||||
import _typeshed
|
||||
import typing_extensions
|
||||
@@ -73,3 +73,93 @@ class BadFive:
|
||||
class BadSix:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
|
||||
|
||||
|
||||
def isolated_scope():
|
||||
from builtins import type as Type
|
||||
|
||||
class ShouldNotError:
|
||||
def __exit__(self, typ: Type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
|
||||
class AllPositionalOnlyArgs:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
|
||||
class BadAllPositionalOnlyArgs:
|
||||
def __exit__(self, typ: type[Exception] | None, exc: BaseException | None, tb: TracebackType | None, /) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType, /) -> None: ...
|
||||
|
||||
# Definitions not in a class scope can do whatever, we don't care
|
||||
def __exit__(self, *args: bool) -> None: ...
|
||||
async def __aexit__(self, *, go_crazy: bytes) -> list[str]: ...
|
||||
|
||||
# Here come the overloads...
|
||||
|
||||
class AcceptableOverload1:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, exc_tb: None) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[BaseException], exc: BaseException, exc_tb: TracebackType) -> None: ...
|
||||
|
||||
# Using `object` or `Unused` in an overload definition is kinda strange,
|
||||
# but let's allow it to be on the safe side
|
||||
class AcceptableOverload2:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, exc_tb: object) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: Unused, exc: BaseException, exc_tb: object) -> None: ...
|
||||
|
||||
class AcceptableOverload3:
|
||||
# Just ignore any overloads that don't have exactly 3 annotated non-self parameters.
|
||||
# We don't have the ability (yet) to do arbitrary checking
|
||||
# of whether one function definition is a subtype of another...
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, exc_tb: bool, weird_extra_arg: bool) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: bool, /, exc: bool, exc_tb: bool, *, keyword_only: str) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, *args: object) -> None: ...
|
||||
|
||||
class AcceptableOverload4:
|
||||
# Same as above
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[Exception], exc: type[Exception], exc_tb: types.TracebackType) -> None: ...
|
||||
@overload
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: type[Exception], exc: type[Exception], exc_tb: types.TracebackType, *, extra: str = "foo") -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, exc_typ: None, exc: None, tb: None) -> None: ...
|
||||
|
||||
class StrangeNumberOfOverloads:
|
||||
# Only one overload? Type checkers will emit an error, but we should just ignore it
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
# More than two overloads? Anything could be going on; again, just ignore all the overloads
|
||||
@overload
|
||||
async def __aexit__(self, arg: bool) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, arg: None, arg2: None, arg3: None) -> None: ...
|
||||
@overload
|
||||
async def __aexit__(self, arg: bool, arg2: bool, arg3: bool) -> None: ...
|
||||
|
||||
# TODO: maybe we should emit an error on this one as well?
|
||||
class BizarreAsyncSyncOverloadMismatch:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
@overload
|
||||
async def __exit__(self, exc_typ: bool, exc: bool, tb: bool) -> None: ...
|
||||
|
||||
class UnacceptableOverload1:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: None, exc: None, tb: None) -> None: ... # Okay
|
||||
@overload
|
||||
def __exit__(self, exc_typ: Exception, exc: Exception, tb: TracebackType) -> None: ... # PYI036
|
||||
|
||||
class UnacceptableOverload2:
|
||||
@overload
|
||||
def __exit__(self, exc_typ: type[BaseException] | None, exc: None, tb: None) -> None: ... # PYI036
|
||||
@overload
|
||||
def __exit__(self, exc_typ: object, exc: Exception, tb: builtins.TracebackType) -> None: ... # PYI036
|
||||
|
||||
@@ -80,5 +80,13 @@ def test_single_list_of_lists(param):
|
||||
@pytest.mark.parametrize("a", [1, 2])
|
||||
@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
@pytest.mark.parametrize("d", [3,])
|
||||
def test_multiple_decorators(a, b, c):
|
||||
@pytest.mark.parametrize(
|
||||
"d",
|
||||
[("3", "4")],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"e",
|
||||
[("3", "4"),],
|
||||
)
|
||||
def test_multiple_decorators(a, b, c, d, e):
|
||||
pass
|
||||
|
||||
@@ -5,3 +5,5 @@ this_should_be_linted = f'double {"quote"} string'
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10546
|
||||
x: "Literal['foo', 'bar']"
|
||||
# https://github.com/astral-sh/ruff/issues/10761
|
||||
f"Before {f'x {x}' if y else f'foo {z}'} after"
|
||||
|
||||
@@ -52,32 +52,32 @@ def f():
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103 (but not fixable)
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
def bool():
|
||||
return False
|
||||
if a:
|
||||
@@ -86,6 +86,14 @@ def f():
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if keys is not None and notice.key not in keys:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
###
|
||||
# Positive cases (preview)
|
||||
###
|
||||
|
||||
@@ -58,3 +58,8 @@ for key in (
|
||||
.keys()
|
||||
):
|
||||
continue
|
||||
|
||||
from builtins import dict as SneakyDict
|
||||
|
||||
d = SneakyDict()
|
||||
key in d.keys() # SIM118
|
||||
|
||||
@@ -21,3 +21,5 @@ for k, v in zip(d2.keys(), d2.values()): # SIM911
|
||||
...
|
||||
|
||||
items = zip(x.keys(), x.values()) # OK
|
||||
|
||||
items.bar = zip(x.keys(), x.values()) # OK
|
||||
|
||||
@@ -11,3 +11,11 @@ from enum import Enum
|
||||
|
||||
class Fine(str, Enum): # Ok
|
||||
__slots__ = ["foo"]
|
||||
|
||||
|
||||
class SubEnum(Enum):
|
||||
pass
|
||||
|
||||
|
||||
class Ok(str, SubEnum): # Ok
|
||||
pass
|
||||
|
||||
@@ -19,3 +19,9 @@ class Bad(Tuple[str, int, float]): # SLOT001
|
||||
|
||||
class Good(Tuple[str, int, float]): # OK
|
||||
__slots__ = ("foo",)
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
class AlsoBad(builtins.tuple[int, int]): # SLOT001
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,14 @@ class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
|
||||
pass
|
||||
|
||||
|
||||
class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
|
||||
pass
|
||||
|
||||
|
||||
class UnusualButOkay(NamedTuple("foo", [("x", int, "y", int)])):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class Good(namedtuple("foo", ["str", "int"])): # OK
|
||||
__slots__ = ("foo",)
|
||||
|
||||
|
||||
@@ -80,3 +80,8 @@ for i in list(foo_list): # OK
|
||||
for i in list(foo_list): # OK
|
||||
if True:
|
||||
del foo_list[i + 1]
|
||||
|
||||
import builtins
|
||||
|
||||
for i in builtins.list(nested_tuple): # PERF101
|
||||
pass
|
||||
|
||||
@@ -81,3 +81,11 @@ def foo():
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[name] = idx # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
from builtins import dict as SneakyDict
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = SneakyDict()
|
||||
for idx, name in enumerate(fruit):
|
||||
result[name] = idx # PERF403
|
||||
|
||||
@@ -445,6 +445,39 @@ def test():
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
class Foo:
|
||||
"""Demo."""
|
||||
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ...
|
||||
@overload
|
||||
def bar(self, x: str) -> str: ...
|
||||
def bar(self, x: int | str) -> int | str:
|
||||
return x
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
@overload
|
||||
def foo(x: int) -> int: ...
|
||||
@overload
|
||||
def foo(x: str) -> str: ...
|
||||
def foo(x: int | str) -> int | str:
|
||||
if not isinstance(x, (int, str)):
|
||||
raise TypeError
|
||||
return x
|
||||
# end
|
||||
|
||||
|
||||
# no error
|
||||
def foo(self, x: int) -> int: ...
|
||||
def bar(self, x: str) -> str: ...
|
||||
def baz(self, x: int | str) -> int | str:
|
||||
return x
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Class(object):
|
||||
|
||||
@@ -489,6 +522,20 @@ class Class:
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Foo:
|
||||
"""Demo."""
|
||||
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ...
|
||||
@overload
|
||||
def bar(self, x: str) -> str:
|
||||
...
|
||||
def bar(self, x: int | str) -> int | str:
|
||||
return x
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
"""Main module."""
|
||||
def fn():
|
||||
@@ -580,6 +627,23 @@ class Test:
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
class A:...
|
||||
class B: ...
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
@overload
|
||||
def fn(a: int) -> int: ...
|
||||
@overload
|
||||
def fn(a: str) -> str: ...
|
||||
|
||||
def fn(a: int | str) -> int | str:
|
||||
...
|
||||
# end
|
||||
|
||||
|
||||
# E303
|
||||
def fn():
|
||||
_ = None
|
||||
|
||||
@@ -138,3 +138,8 @@ np.dtype(int) == float
|
||||
|
||||
#: E721
|
||||
dtype == float
|
||||
|
||||
import builtins
|
||||
|
||||
if builtins.type(res) == memoryview: # E721
|
||||
pass
|
||||
|
||||
@@ -25,3 +25,9 @@ def non_ascii():
|
||||
|
||||
def all_caps():
|
||||
"""th•s is not capitalized."""
|
||||
|
||||
def single_word():
|
||||
"""singleword."""
|
||||
|
||||
def single_word_no_dot():
|
||||
"""singleword"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||
|
||||
from typing import Optional, TypeAlias, Union
|
||||
from typing import Generic, NewType, Optional, TypeAlias, TypeVar, Union
|
||||
|
||||
__version__: str
|
||||
__author__: str
|
||||
@@ -33,6 +33,19 @@ class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
# Generic bases can have forward references in stubs
|
||||
class Foo(Generic[T]): ...
|
||||
T = TypeVar("T")
|
||||
class Bar(Foo[Baz]): ...
|
||||
class Baz: ...
|
||||
|
||||
# bases in general can be forward references in stubs
|
||||
class Eggs(Spam): ...
|
||||
class Spam: ...
|
||||
|
||||
# NewType can have forward references
|
||||
MyNew = NewType("MyNew", MyClass)
|
||||
|
||||
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||
class MyClass:
|
||||
foo: int
|
||||
@@ -42,3 +55,6 @@ class MyClass:
|
||||
baz: MyClass
|
||||
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
eggs = "baz" # always okay
|
||||
|
||||
class Blah:
|
||||
class Blah2(Blah): ...
|
||||
|
||||
13
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_3.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_3.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Respect `# noqa` directives on `__all__` definitions."""
|
||||
|
||||
__all__ = [ # noqa: F822
|
||||
"Bernoulli",
|
||||
"Beta",
|
||||
"Binomial",
|
||||
]
|
||||
|
||||
|
||||
__all__ += [
|
||||
"ContinuousBernoulli", # noqa: F822
|
||||
"ExponentialFamily",
|
||||
]
|
||||
@@ -4,3 +4,8 @@ def f() -> None:
|
||||
|
||||
def g() -> None:
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
def h() -> None:
|
||||
NotImplementedError = "foo"
|
||||
raise NotImplemented
|
||||
|
||||
@@ -9,3 +9,22 @@ x = 1
|
||||
x = 1 # noqa: F401, W203
|
||||
# noqa: F401
|
||||
# noqa: F401, W203
|
||||
|
||||
# OK
|
||||
x = 2 # noqa: X100
|
||||
x = 2 # noqa:X100
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa X100
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa X100, X200
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa : X300
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa : X400
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa :X500
|
||||
|
||||
@@ -32,3 +32,6 @@ pathlib.Path(NAME).open(mode)
|
||||
pathlib.Path(NAME).open("rwx") # [bad-open-mode]
|
||||
pathlib.Path(NAME).open(mode="rwx") # [bad-open-mode]
|
||||
pathlib.Path(NAME).open("rwx", encoding="utf-8") # [bad-open-mode]
|
||||
|
||||
import builtins
|
||||
builtins.open(NAME, "Ua", encoding="utf-8")
|
||||
|
||||
44
crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
class Wolf:
|
||||
@staticmethod
|
||||
def eat(self): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
|
||||
class Wolf:
|
||||
@staticmethod
|
||||
def eat(sheep):
|
||||
pass
|
||||
|
||||
|
||||
class Sheep:
|
||||
@staticmethod
|
||||
def eat(cls, x, y, z): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def sleep(self, x, y, z): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
def grow(self, x, y, z):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def graze(cls, x, y, z):
|
||||
pass
|
||||
|
||||
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def eat(x, self, z):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def sleep(x, cls, z):
|
||||
pass
|
||||
|
||||
def grow(self, x, y, z):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def graze(cls, x, y, z):
|
||||
pass
|
||||
161
crates/ruff_linter/resources/test/fixtures/pylint/if_stmt_min_max.py
vendored
Normal file
161
crates/ruff_linter/resources/test/fixtures/pylint/if_stmt_min_max.py
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, redefined-outer-name
|
||||
|
||||
value = 10
|
||||
value2 = 0
|
||||
value3 = 3
|
||||
|
||||
# Positive
|
||||
if value < 10: # [max-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value <= 10: # [max-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value < value2: # [max-instead-of-if]
|
||||
value = value2
|
||||
|
||||
if value > 10: # [min-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value >= 10: # [min-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value > value2: # [min-instead-of-if]
|
||||
value = value2
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.value = 13
|
||||
|
||||
|
||||
A1 = A()
|
||||
if A1.value < 10: # [max-instead-of-if]
|
||||
A1.value = 10
|
||||
|
||||
if A1.value > 10: # [min-instead-of-if]
|
||||
A1.value = 10
|
||||
|
||||
|
||||
class AA:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __gt__(self, b):
|
||||
return self.value > b
|
||||
|
||||
def __ge__(self, b):
|
||||
return self.value >= b
|
||||
|
||||
def __lt__(self, b):
|
||||
return self.value < b
|
||||
|
||||
def __le__(self, b):
|
||||
return self.value <= b
|
||||
|
||||
|
||||
A1 = AA(0)
|
||||
A2 = AA(3)
|
||||
|
||||
if A2 < A1: # [max-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 <= A1: # [max-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 > A1: # [min-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 >= A1: # [min-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
# Negative
|
||||
if value < 10:
|
||||
value = 2
|
||||
|
||||
if value <= 3:
|
||||
value = 5
|
||||
|
||||
if value < 10:
|
||||
value = 2
|
||||
value2 = 3
|
||||
|
||||
if value < value2:
|
||||
value = value3
|
||||
|
||||
if value < 5:
|
||||
value = value3
|
||||
|
||||
if 2 < value <= 3:
|
||||
value = 1
|
||||
|
||||
if value < 10:
|
||||
value = 10
|
||||
else:
|
||||
value = 3
|
||||
|
||||
if value <= 3:
|
||||
value = 5
|
||||
elif value == 3:
|
||||
value = 2
|
||||
|
||||
if value > 10:
|
||||
value = 2
|
||||
|
||||
if value >= 3:
|
||||
value = 5
|
||||
|
||||
if value > 10:
|
||||
value = 2
|
||||
value2 = 3
|
||||
|
||||
if value > value2:
|
||||
value = value3
|
||||
|
||||
if value > 5:
|
||||
value = value3
|
||||
|
||||
if 2 > value >= 3:
|
||||
value = 1
|
||||
|
||||
if value > 10:
|
||||
value = 10
|
||||
else:
|
||||
value = 3
|
||||
|
||||
if value >= 3:
|
||||
value = 5
|
||||
elif value == 3:
|
||||
value = 2
|
||||
|
||||
# Parenthesized expressions
|
||||
if value.attr > 3:
|
||||
(
|
||||
value.
|
||||
attr
|
||||
) = 3
|
||||
|
||||
class Foo:
|
||||
_min = 0
|
||||
_max = 0
|
||||
|
||||
def foo(self, value) -> None:
|
||||
if value < self._min:
|
||||
self._min = value
|
||||
if value > self._max:
|
||||
self._max = value
|
||||
|
||||
if self._min < value:
|
||||
self._min = value
|
||||
if self._max > value:
|
||||
self._max = value
|
||||
|
||||
if value <= self._min:
|
||||
self._min = value
|
||||
if value >= self._max:
|
||||
self._max = value
|
||||
|
||||
if self._min <= value:
|
||||
self._min = value
|
||||
if self._max >= value:
|
||||
self._max = value
|
||||
65
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bytes.py
vendored
Normal file
65
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bytes.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
|
||||
class Float:
|
||||
def __bytes__(self):
|
||||
return 3.05 # [invalid-bytes-return]
|
||||
|
||||
|
||||
class Int:
|
||||
def __bytes__(self):
|
||||
return 0 # [invalid-bytes-return]
|
||||
|
||||
|
||||
class Str:
|
||||
def __bytes__(self):
|
||||
return "some bytes" # [invalid-bytes-return]
|
||||
|
||||
|
||||
class BytesNoReturn:
|
||||
def __bytes__(self):
|
||||
print("ruff") # [invalid-bytes-return]
|
||||
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_bytes():
|
||||
return "some string"
|
||||
|
||||
|
||||
class ComplexReturn:
|
||||
def __bytes__(self):
|
||||
return return_bytes() # [invalid-bytes-return]
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
|
||||
class Bytes:
|
||||
def __bytes__(self):
|
||||
return b"some bytes"
|
||||
|
||||
|
||||
class Bytes2:
|
||||
def __bytes__(self):
|
||||
x = b"some bytes"
|
||||
return x
|
||||
|
||||
|
||||
class Bytes3:
|
||||
def __bytes__(self): ...
|
||||
|
||||
|
||||
class Bytes4:
|
||||
def __bytes__(self):
|
||||
pass
|
||||
|
||||
|
||||
class Bytes5:
|
||||
def __bytes__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Bytes6:
|
||||
def __bytes__(self):
|
||||
print("raise some error")
|
||||
raise NotImplementedError
|
||||
65
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_hash.py
vendored
Normal file
65
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_hash.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
|
||||
class Bool:
|
||||
def __hash__(self):
|
||||
return True # [invalid-hash-return]
|
||||
|
||||
|
||||
class Float:
|
||||
def __hash__(self):
|
||||
return 3.05 # [invalid-hash-return]
|
||||
|
||||
|
||||
class Str:
|
||||
def __hash__(self):
|
||||
return "ruff" # [invalid-hash-return]
|
||||
|
||||
|
||||
class HashNoReturn:
|
||||
def __hash__(self):
|
||||
print("ruff") # [invalid-hash-return]
|
||||
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_int():
|
||||
return "3"
|
||||
|
||||
|
||||
class ComplexReturn:
|
||||
def __hash__(self):
|
||||
return return_int() # [invalid-hash-return]
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
|
||||
class Hash:
|
||||
def __hash__(self):
|
||||
return 7741
|
||||
|
||||
|
||||
class Hash2:
|
||||
def __hash__(self):
|
||||
x = 7741
|
||||
return x
|
||||
|
||||
|
||||
class Hash3:
|
||||
def __hash__(self): ...
|
||||
|
||||
|
||||
class Has4:
|
||||
def __hash__(self):
|
||||
pass
|
||||
|
||||
|
||||
class Hash5:
|
||||
def __hash__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HashWrong6:
|
||||
def __hash__(self):
|
||||
print("raise some error")
|
||||
raise NotImplementedError
|
||||
73
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_index.py
vendored
Normal file
73
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_index.py
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
|
||||
class Bool:
|
||||
"""pylint would not raise, but ruff does - see explanation in the docs"""
|
||||
|
||||
def __index__(self):
|
||||
return True # [invalid-index-return]
|
||||
|
||||
|
||||
class Float:
|
||||
def __index__(self):
|
||||
return 3.05 # [invalid-index-return]
|
||||
|
||||
|
||||
class Dict:
|
||||
def __index__(self):
|
||||
return {"1": "1"} # [invalid-index-return]
|
||||
|
||||
|
||||
class Str:
|
||||
def __index__(self):
|
||||
return "ruff" # [invalid-index-return]
|
||||
|
||||
|
||||
class IndexNoReturn:
|
||||
def __index__(self):
|
||||
print("ruff") # [invalid-index-return]
|
||||
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_index():
|
||||
return "3"
|
||||
|
||||
|
||||
class ComplexReturn:
|
||||
def __index__(self):
|
||||
return return_index() # [invalid-index-return]
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
|
||||
class Index:
|
||||
def __index__(self):
|
||||
return 0
|
||||
|
||||
|
||||
class Index2:
|
||||
def __index__(self):
|
||||
x = 1
|
||||
return x
|
||||
|
||||
|
||||
class Index3:
|
||||
def __index__(self):
|
||||
...
|
||||
|
||||
|
||||
class Index4:
|
||||
def __index__(self):
|
||||
pass
|
||||
|
||||
|
||||
class Index5:
|
||||
def __index__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Index6:
|
||||
def __index__(self):
|
||||
print("raise some error")
|
||||
raise NotImplementedError
|
||||
70
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_length.py
vendored
Normal file
70
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_length.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
|
||||
class Bool:
|
||||
def __len__(self):
|
||||
return True # [invalid-length-return]
|
||||
|
||||
|
||||
class Float:
|
||||
def __len__(self):
|
||||
return 3.05 # [invalid-length-return]
|
||||
|
||||
|
||||
class Str:
|
||||
def __len__(self):
|
||||
return "ruff" # [invalid-length-return]
|
||||
|
||||
|
||||
class LengthNoReturn:
|
||||
def __len__(self):
|
||||
print("ruff") # [invalid-length-return]
|
||||
|
||||
|
||||
class LengthNegative:
|
||||
def __len__(self):
|
||||
return -42 # [invalid-length-return]
|
||||
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_int():
|
||||
return "3"
|
||||
|
||||
|
||||
class ComplexReturn:
|
||||
def __len__(self):
|
||||
return return_int() # [invalid-length-return]
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
|
||||
class Length:
|
||||
def __len__(self):
|
||||
return 42
|
||||
|
||||
|
||||
class Length2:
|
||||
def __len__(self):
|
||||
x = 42
|
||||
return x
|
||||
|
||||
|
||||
class Length3:
|
||||
def __len__(self): ...
|
||||
|
||||
|
||||
class Length4:
|
||||
def __len__(self):
|
||||
pass
|
||||
|
||||
|
||||
class Length5:
|
||||
def __len__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Length6:
|
||||
def __len__(self):
|
||||
print("raise some error")
|
||||
raise NotImplementedError
|
||||
@@ -1,36 +1,60 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
|
||||
class Float:
|
||||
def __str__(self):
|
||||
return 3.05
|
||||
|
||||
|
||||
class Int:
|
||||
def __str__(self):
|
||||
return 1
|
||||
|
||||
|
||||
class Int2:
|
||||
def __str__(self):
|
||||
return 0
|
||||
|
||||
|
||||
class Bool:
|
||||
def __str__(self):
|
||||
return False
|
||||
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_int():
|
||||
return 3
|
||||
|
||||
|
||||
class ComplexReturn:
|
||||
def __str__(self):
|
||||
return return_int()
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
|
||||
class Str:
|
||||
def __str__(self):
|
||||
return "ruff"
|
||||
|
||||
|
||||
class Str2:
|
||||
def __str__(self):
|
||||
x = "ruff"
|
||||
return x
|
||||
|
||||
|
||||
class Str3:
|
||||
def __str__(self): ...
|
||||
|
||||
|
||||
class Str4:
|
||||
def __str__(self):
|
||||
raise RuntimeError("__str__ not allowed")
|
||||
|
||||
|
||||
class Str5:
|
||||
def __str__(self): # PLE0307 (returns None if x <= 0)
|
||||
if x > 0:
|
||||
raise RuntimeError("__str__ not allowed")
|
||||
|
||||
@@ -47,6 +47,12 @@ if y == np.nan:
|
||||
if y == npy_nan:
|
||||
pass
|
||||
|
||||
import builtins
|
||||
|
||||
# PLW0117
|
||||
if x == builtins.float("nan"):
|
||||
pass
|
||||
|
||||
# OK
|
||||
if math.isnan(x):
|
||||
pass
|
||||
|
||||
@@ -39,3 +39,6 @@ max(max(tuples_list))
|
||||
|
||||
# Starred argument should be copied as it is.
|
||||
max(1, max(*a))
|
||||
|
||||
import builtins
|
||||
builtins.min(1, min(2, 3))
|
||||
|
||||
58
crates/ruff_linter/resources/test/fixtures/pylint/non_augmented_assignment.py
vendored
Normal file
58
crates/ruff_linter/resources/test/fixtures/pylint/non_augmented_assignment.py
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# Errors
|
||||
some_string = "some string"
|
||||
index, a_number, to_multiply, to_divide, to_cube, timeDiffSeconds, flags = (
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
0x3,
|
||||
)
|
||||
a_list = [1, 2]
|
||||
some_set = {"elem"}
|
||||
mat1, mat2 = None, None
|
||||
|
||||
some_string = some_string + "a very long end of string"
|
||||
index = index - 1
|
||||
a_list = a_list + ["to concat"]
|
||||
some_set = some_set | {"to concat"}
|
||||
to_multiply = to_multiply * 5
|
||||
to_multiply = 5 * to_multiply
|
||||
to_multiply = to_multiply * to_multiply
|
||||
to_divide = to_divide / 5
|
||||
to_divide = to_divide // 5
|
||||
to_cube = to_cube**3
|
||||
to_cube = 3**to_cube
|
||||
to_cube = to_cube**to_cube
|
||||
timeDiffSeconds = timeDiffSeconds % 60
|
||||
flags = flags & 0x1
|
||||
flags = flags | 0x1
|
||||
flags = flags ^ 0x1
|
||||
flags = flags << 1
|
||||
flags = flags >> 1
|
||||
mat1 = mat1 @ mat2
|
||||
a_list[1] = a_list[1] + 1
|
||||
|
||||
a_list[0:2] = a_list[0:2] * 3
|
||||
a_list[:2] = a_list[:2] * 3
|
||||
a_list[1:] = a_list[1:] * 3
|
||||
a_list[:] = a_list[:] * 3
|
||||
|
||||
index = index * (index + 10)
|
||||
|
||||
|
||||
class T:
|
||||
def t(self):
|
||||
self.a = self.a + 1
|
||||
|
||||
|
||||
obj = T()
|
||||
obj.a = obj.a + 1
|
||||
|
||||
# OK
|
||||
a_list[0] = a_list[:] * 3
|
||||
index = a_number = a_number + 1
|
||||
a_number = index = a_number + 1
|
||||
index = index * index + 10
|
||||
some_string = "a very long start to the string" + some_string
|
||||
43
crates/ruff_linter/resources/test/fixtures/pylint/self_or_cls_assignment.py
vendored
Normal file
43
crates/ruff_linter/resources/test/fixtures/pylint/self_or_cls_assignment.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
class Fruit:
|
||||
@classmethod
|
||||
def list_fruits(cls) -> None:
|
||||
cls = "apple" # PLW0642
|
||||
cls: Fruit = "apple" # PLW0642
|
||||
cls += "orange" # PLW0642
|
||||
*cls = "banana" # PLW0642
|
||||
cls, blah = "apple", "orange" # PLW0642
|
||||
blah, (cls, blah2) = "apple", ("orange", "banana") # PLW0642
|
||||
blah, [cls, blah2] = "apple", ("orange", "banana") # PLW0642
|
||||
|
||||
@classmethod
|
||||
def add_fruits(cls, fruits, /) -> None:
|
||||
cls = fruits # PLW0642
|
||||
|
||||
def print_color(self) -> None:
|
||||
self = "red" # PLW0642
|
||||
self: Self = "red" # PLW0642
|
||||
self += "blue" # PLW0642
|
||||
*self = "blue" # PLW0642
|
||||
self, blah = "red", "blue" # PLW0642
|
||||
blah, (self, blah2) = "apple", ("orange", "banana") # PLW0642
|
||||
blah, [self, blah2] = "apple", ("orange", "banana") # PLW0642
|
||||
|
||||
def print_color(self, color, /) -> None:
|
||||
self = color
|
||||
|
||||
def ok(self) -> None:
|
||||
cls = None # OK because the rule looks for the name in the signature
|
||||
|
||||
@classmethod
|
||||
def ok(cls) -> None:
|
||||
self = None
|
||||
|
||||
@staticmethod
|
||||
def list_fruits_static(self, cls) -> None:
|
||||
self = "apple" # Ok
|
||||
cls = "banana" # Ok
|
||||
|
||||
|
||||
def list_fruits(self, cls) -> None:
|
||||
self = "apple" # Ok
|
||||
cls = "banana" # Ok
|
||||
@@ -152,3 +152,9 @@ object = A
|
||||
|
||||
class B(object):
|
||||
...
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
class Unusual(builtins.object):
|
||||
...
|
||||
|
||||
@@ -117,3 +117,10 @@ path = "%s-%s-%s.pem" % (
|
||||
cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date
|
||||
hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix
|
||||
)
|
||||
|
||||
# UP031 (no longer false negatives; now offer potentially unsafe fixes)
|
||||
'Hello %s' % bar
|
||||
|
||||
'Hello %s' % bar.baz
|
||||
|
||||
'Hello %s' % bar['bop']
|
||||
|
||||
@@ -32,10 +32,3 @@ pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
|
||||
"%(1)s" % {1: 2, "1": 2}
|
||||
|
||||
"%(and)s" % {"and": 2}
|
||||
|
||||
# OK (arguably false negatives)
|
||||
'Hello %s' % bar
|
||||
|
||||
'Hello %s' % bar.baz
|
||||
|
||||
'Hello %s' % bar['bop']
|
||||
|
||||
13
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP042.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP042.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class A(str, Enum): ...
|
||||
|
||||
|
||||
class B(Enum, str): ...
|
||||
|
||||
|
||||
class D(int, str, Enum): ...
|
||||
|
||||
|
||||
class E(str, int, Enum): ...
|
||||
@@ -28,10 +28,6 @@ with open("file.txt", encoding="utf8") as f:
|
||||
with open("file.txt", errors="ignore") as f:
|
||||
x = f.read()
|
||||
|
||||
# FURB101
|
||||
with open("file.txt", errors="ignore", mode="rb") as f:
|
||||
x = f.read()
|
||||
|
||||
# FURB101
|
||||
with open("file.txt", mode="r") as f: # noqa: FURB120
|
||||
x = f.read()
|
||||
@@ -60,6 +56,11 @@ with foo() as a, open("file.txt") as b, foo() as c:
|
||||
|
||||
# Non-errors.
|
||||
|
||||
# Path.read_bytes does not support any kwargs
|
||||
with open("file.txt", errors="ignore", mode="rb") as f:
|
||||
x = f.read()
|
||||
|
||||
|
||||
f2 = open("file2.txt")
|
||||
with open("file.txt") as f:
|
||||
x = f2.read()
|
||||
|
||||
147
crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py
vendored
Normal file
147
crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
def foo():
|
||||
...
|
||||
|
||||
|
||||
def bar(x):
|
||||
...
|
||||
|
||||
|
||||
# Errors.
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w") as f:
|
||||
f.write("test")
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "wb") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", mode="wb") as f:
|
||||
f.write(b"abc")
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", encoding="utf8") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", errors="ignore") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", mode="w") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open(foo(), "wb") as f:
|
||||
# The body of `with` is non-trivial, but the recommendation holds.
|
||||
bar("pre")
|
||||
f.write(bar())
|
||||
bar("post")
|
||||
print("Done")
|
||||
|
||||
# FURB103
|
||||
with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||
a.write(x)
|
||||
b.write(y)
|
||||
|
||||
# FURB103
|
||||
with foo() as a, open("file.txt", "w") as b, foo() as c:
|
||||
# We have other things in here, multiple with items, but the user
|
||||
# writes a single time to file and that bit they can replace.
|
||||
bar(a)
|
||||
b.write(bar(bar(a + x)))
|
||||
bar(c)
|
||||
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", newline="\r\n") as f:
|
||||
f.write(foobar)
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
|
||||
# FURB103
|
||||
with builtins.open("file.txt", "w", newline="\r\n") as f:
|
||||
f.write(foobar)
|
||||
|
||||
|
||||
from builtins import open as o
|
||||
|
||||
|
||||
# FURB103
|
||||
with o("file.txt", "w", newline="\r\n") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# Non-errors.
|
||||
|
||||
with open("file.txt", errors="ignore", mode="wb") as f:
|
||||
# Path.write_bytes() does not support errors
|
||||
f.write(foobar)
|
||||
|
||||
f2 = open("file2.txt", "w")
|
||||
with open("file.txt", "w") as f:
|
||||
f2.write(x)
|
||||
|
||||
# mode is dynamic
|
||||
with open("file.txt", foo()) as f:
|
||||
f.write(x)
|
||||
|
||||
# keyword mode is incorrect
|
||||
with open("file.txt", mode="a+") as f:
|
||||
f.write(x)
|
||||
|
||||
# enables line buffering, not supported in write_text()
|
||||
with open("file.txt", buffering=1) as f:
|
||||
f.write(x)
|
||||
|
||||
# dont mistake "newline" for "mode"
|
||||
with open("file.txt", newline="wb") as f:
|
||||
f.write(x)
|
||||
|
||||
# I guess we can possibly also report this case, but the question
|
||||
# is why the user would put "w+" here in the first place.
|
||||
with open("file.txt", "w+") as f:
|
||||
f.write(x)
|
||||
|
||||
# Even though we write the whole file, we do other things.
|
||||
with open("file.txt", "w") as f:
|
||||
f.write(x)
|
||||
f.seek(0)
|
||||
x += f.read(100)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open(*filename, mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open(**kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open("file.txt", **kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open("file.txt", mode="w", **kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This could error (but doesn't), since it can't contain unsupported arguments, like
|
||||
# `buffering`.
|
||||
with open(*filename, mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# This could error (but doesn't), since it can't contain unsupported arguments, like
|
||||
# `buffering`.
|
||||
with open(*filename, file="file.txt", mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# Loops imply multiple writes
|
||||
with open("file.txt", "w") as f:
|
||||
while x < 0:
|
||||
f.write(foobar)
|
||||
|
||||
with open("file.txt", "w") as f:
|
||||
for line in text:
|
||||
f.write(line)
|
||||
40
crates/ruff_linter/resources/test/fixtures/refurb/FURB110.py
vendored
Normal file
40
crates/ruff_linter/resources/test/fixtures/refurb/FURB110.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
z = x if x else y # FURB110
|
||||
|
||||
z = x \
|
||||
if x else y # FURB110
|
||||
|
||||
z = x if x \
|
||||
else \
|
||||
y # FURB110
|
||||
|
||||
z = x() if x() else y() # FURB110
|
||||
|
||||
# FURB110
|
||||
z = x if (
|
||||
# Test for x.
|
||||
x
|
||||
) else (
|
||||
# Test for y.
|
||||
y
|
||||
)
|
||||
|
||||
# FURB110
|
||||
z = (
|
||||
x if (
|
||||
# Test for x.
|
||||
x
|
||||
) else (
|
||||
# Test for y.
|
||||
y
|
||||
)
|
||||
)
|
||||
|
||||
# FURB110
|
||||
z = (
|
||||
x if
|
||||
# If true, use x.
|
||||
x
|
||||
# Otherwise, use y.
|
||||
else
|
||||
y
|
||||
)
|
||||
@@ -27,6 +27,10 @@ op_gte = lambda x, y: x >= y
|
||||
op_is = lambda x, y: x is y
|
||||
op_isnot = lambda x, y: x is not y
|
||||
op_in = lambda x, y: y in x
|
||||
op_itemgetter = lambda x: x[0]
|
||||
op_itemgetter = lambda x: (x[0], x[1], x[2])
|
||||
op_itemgetter = lambda x: (x[1:], x[2])
|
||||
op_itemgetter = lambda x: x[:]
|
||||
|
||||
|
||||
def op_not2(x):
|
||||
@@ -41,21 +45,31 @@ class Adder:
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
|
||||
# OK.
|
||||
op_add3 = lambda x, y = 1: x + y
|
||||
op_add3 = lambda x, y=1: x + y
|
||||
op_neg2 = lambda x, y: y - x
|
||||
op_notin = lambda x, y: y not in x
|
||||
op_and = lambda x, y: y and x
|
||||
op_or = lambda x, y: y or x
|
||||
op_in = lambda x, y: x in y
|
||||
op_itemgetter = lambda x: (1, x[1], x[2])
|
||||
op_itemgetter = lambda x: (x.y, x[1], x[2])
|
||||
op_itemgetter = lambda x, y: (x[0], y[0])
|
||||
op_itemgetter = lambda x, y: (x[0], y[0])
|
||||
op_itemgetter = lambda x: ()
|
||||
op_itemgetter = lambda x: (*x[0], x[1])
|
||||
op_itemgetter = lambda x: (x[0],)
|
||||
|
||||
|
||||
def op_neg3(x, y):
|
||||
return y - x
|
||||
|
||||
def op_add4(x, y = 1):
|
||||
|
||||
def op_add4(x, y=1):
|
||||
return x + y
|
||||
|
||||
|
||||
def op_add5(x, y):
|
||||
print("op_add5")
|
||||
return x + y
|
||||
|
||||
@@ -41,6 +41,22 @@ def func():
|
||||
pass
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
|
||||
with builtins.open("FURB129.py") as f:
|
||||
for line in f.readlines():
|
||||
pass
|
||||
|
||||
|
||||
from builtins import open as o
|
||||
|
||||
|
||||
with o("FURB129.py") as f:
|
||||
for line in f.readlines():
|
||||
pass
|
||||
|
||||
|
||||
# False positives
|
||||
def func(f):
|
||||
for _line in f.readlines():
|
||||
|
||||
@@ -49,6 +49,14 @@ def yes_five(x: Dict[int, str]):
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
from builtins import list as SneakyList
|
||||
|
||||
|
||||
sneaky = SneakyList()
|
||||
# FURB131
|
||||
del sneaky[:]
|
||||
|
||||
# these should not
|
||||
|
||||
del names["key"]
|
||||
|
||||
48
crates/ruff_linter/resources/test/fixtures/refurb/FURB192.py
vendored
Normal file
48
crates/ruff_linter/resources/test/fixtures/refurb/FURB192.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Errors
|
||||
|
||||
sorted(l)[0]
|
||||
|
||||
sorted(l)[-1]
|
||||
|
||||
sorted(l, reverse=False)[-1]
|
||||
|
||||
sorted(l, key=lambda x: x)[0]
|
||||
|
||||
sorted(l, key=key_fn)[0]
|
||||
|
||||
sorted([1, 2, 3])[0]
|
||||
|
||||
# Unsafe
|
||||
|
||||
sorted(l, key=key_fn, reverse=True)[-1]
|
||||
|
||||
sorted(l, reverse=True)[0]
|
||||
|
||||
sorted(l, reverse=True)[-1]
|
||||
|
||||
# Non-errors
|
||||
|
||||
sorted(l, reverse=foo())[0]
|
||||
|
||||
sorted(l)[1]
|
||||
|
||||
sorted(get_list())[1]
|
||||
|
||||
sorted()[0]
|
||||
|
||||
sorted(l)[1]
|
||||
|
||||
sorted(l)[-2]
|
||||
|
||||
b = True
|
||||
|
||||
sorted(l, reverse=b)[0]
|
||||
|
||||
sorted(l, invalid_kwarg=True)[0]
|
||||
|
||||
|
||||
def sorted():
|
||||
pass
|
||||
|
||||
|
||||
sorted(l)[0]
|
||||
@@ -12,6 +12,8 @@ dict.fromkeys(pierogi_fillings, {})
|
||||
dict.fromkeys(pierogi_fillings, set())
|
||||
dict.fromkeys(pierogi_fillings, {"pre": "populated!"})
|
||||
dict.fromkeys(pierogi_fillings, dict())
|
||||
import builtins
|
||||
builtins.dict.fromkeys(pierogi_fillings, dict())
|
||||
|
||||
# Okay.
|
||||
dict.fromkeys(pierogi_fillings)
|
||||
|
||||
75
crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py
vendored
Normal file
75
crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
|
||||
async def pass_1a(): # OK: awaits a coroutine
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def pass_1b(): # OK: awaits a coroutine
|
||||
def foo(optional_arg=await bar()):
|
||||
pass
|
||||
|
||||
|
||||
async def pass_2(): # OK: uses an async context manager
|
||||
async with None as i:
|
||||
pass
|
||||
|
||||
|
||||
async def pass_3(): # OK: uses an async loop
|
||||
async for i in []:
|
||||
pass
|
||||
|
||||
|
||||
class Foo:
|
||||
async def pass_4(self): # OK: method of a class
|
||||
pass
|
||||
|
||||
|
||||
def foo():
|
||||
async def pass_5(): # OK: uses an await
|
||||
await bla
|
||||
|
||||
|
||||
async def pass_6(): # OK: just a stub
|
||||
...
|
||||
|
||||
|
||||
async def fail_1a(): # RUF029
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def fail_1b(): # RUF029: yield does not require async
|
||||
yield "hello"
|
||||
|
||||
|
||||
async def fail_2(): # RUF029
|
||||
with None as i:
|
||||
pass
|
||||
|
||||
|
||||
async def fail_3(): # RUF029
|
||||
for i in []:
|
||||
pass
|
||||
|
||||
return foo
|
||||
|
||||
|
||||
async def fail_4a(): # RUF029: the /outer/ function does not await
|
||||
async def foo():
|
||||
await bla
|
||||
|
||||
|
||||
async def fail_4b(): # RUF029: the /outer/ function does not await
|
||||
class Foo:
|
||||
async def foo(self):
|
||||
await bla
|
||||
|
||||
|
||||
def foo():
|
||||
async def fail_4c(): # RUF029: the /inner/ function does not await
|
||||
pass
|
||||
|
||||
|
||||
async def test():
|
||||
return [check async for check in async_func()]
|
||||
@@ -1,2 +1,3 @@
|
||||
import os
|
||||
import foo # noqa: F401
|
||||
import bar # noqa
|
||||
|
||||
@@ -30,6 +30,9 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EnumerateForLoop) {
|
||||
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
|
||||
}
|
||||
if checker.enabled(Rule::LoopIteratorMutation) {
|
||||
flake8_bugbear::rules::loop_iterator_mutation(checker, stmt_for);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,19 @@ use crate::rules::{
|
||||
pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::AsyncioDanglingTask,
|
||||
Rule::BadStaticmethodArgument,
|
||||
Rule::BuiltinAttributeShadowing,
|
||||
Rule::GlobalVariableNotAssigned,
|
||||
Rule::ImportPrivateName,
|
||||
Rule::ImportShadowedByLoopVar,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
Rule::NoSelfUse,
|
||||
Rule::RedefinedArgumentFromLocal,
|
||||
Rule::RedefinedWhileUnused,
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::SingledispatchMethod,
|
||||
Rule::SingledispatchmethodFunction,
|
||||
Rule::TooManyLocals,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
@@ -31,19 +35,16 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UndefinedLocal,
|
||||
Rule::UnusedAnnotation,
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::BuiltinAttributeShadowing,
|
||||
Rule::UnusedFunctionArgument,
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedLambdaArgument,
|
||||
Rule::UnusedMethodArgument,
|
||||
Rule::UnusedPrivateProtocol,
|
||||
Rule::UnusedPrivateTypeAlias,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
Rule::SingledispatchMethod,
|
||||
Rule::SingledispatchmethodFunction,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -424,6 +425,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::BadStaticmethodArgument) {
|
||||
pylint::rules::bad_staticmethod_argument(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
|
||||
@@ -32,10 +32,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -48,7 +47,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py310
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -60,9 +59,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
// Ex) list[...]
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& typing::is_pep585_generic(value, &checker.semantic)
|
||||
{
|
||||
@@ -127,6 +125,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::PotentialIndexError) {
|
||||
pylint::rules::potential_index_error(checker, value, slice);
|
||||
}
|
||||
if checker.enabled(Rule::SortedMinMax) {
|
||||
refurb::rules::sorted_min_max(checker, subscript);
|
||||
}
|
||||
|
||||
pandas_vet::rules::subscript(checker, value, expr);
|
||||
}
|
||||
@@ -186,10 +187,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
typing::to_pep585_generic(expr, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -200,7 +200,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -250,7 +250,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprContext::Del => {}
|
||||
_ => {}
|
||||
}
|
||||
if checker.enabled(Rule::SixPY3) {
|
||||
flake8_2020::rules::name_or_attribute(checker, expr);
|
||||
@@ -270,10 +270,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -286,7 +285,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -1176,9 +1175,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}) => {
|
||||
// Ex) `str | None`
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
{
|
||||
flake8_future_annotations::rules::future_required_type_annotation(
|
||||
@@ -1363,6 +1361,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IfExprMinMax) {
|
||||
refurb::rules::if_expr_min_max(checker, if_exp);
|
||||
}
|
||||
if checker.enabled(Rule::IfExpInsteadOfOrOperator) {
|
||||
refurb::rules::if_exp_instead_of_or_operator(checker, if_exp);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
|
||||
@@ -95,10 +95,22 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::InvalidBoolReturnType) {
|
||||
pylint::rules::invalid_bool_return(checker, name, body);
|
||||
pylint::rules::invalid_bool_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidLengthReturnType) {
|
||||
pylint::rules::invalid_length_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidBytesReturnType) {
|
||||
pylint::rules::invalid_bytes_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidIndexReturnType) {
|
||||
pylint::rules::invalid_index_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidHashReturnType) {
|
||||
pylint::rules::invalid_hash_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidStrReturnType) {
|
||||
pylint::rules::invalid_str_return(checker, name, body);
|
||||
pylint::rules::invalid_str_return(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidFunctionName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::invalid_function_name(
|
||||
@@ -146,7 +158,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
parameters,
|
||||
type_params.as_ref(),
|
||||
type_params.as_deref(),
|
||||
);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
@@ -162,7 +174,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BadExitAnnotation) {
|
||||
flake8_pyi::rules::bad_exit_annotation(checker, *is_async, name, parameters);
|
||||
flake8_pyi::rules::bad_exit_annotation(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
||||
@@ -348,6 +360,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SslWithBadDefaults) {
|
||||
flake8_bandit::rules::ssl_with_bad_defaults(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedAsync) {
|
||||
ruff::rules::unused_async(checker, function_def);
|
||||
}
|
||||
}
|
||||
Stmt::Return(_) => {
|
||||
if checker.enabled(Rule::ReturnOutsideFunction) {
|
||||
@@ -406,6 +421,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UselessObjectInheritance) {
|
||||
pyupgrade::rules::useless_object_inheritance(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::ReplaceStrEnum) {
|
||||
if checker.settings.target_version >= PythonVersion::Py311 {
|
||||
pyupgrade::rules::replace_str_enum(checker, class_def);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryClassParentheses) {
|
||||
pyupgrade::rules::unnecessary_class_parentheses(checker, class_def);
|
||||
}
|
||||
@@ -601,7 +621,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::manual_from_import(checker, stmt, alias, names);
|
||||
}
|
||||
if checker.enabled(Rule::ImportSelf) {
|
||||
if let Some(diagnostic) = pylint::rules::import_self(alias, checker.module_path)
|
||||
if let Some(diagnostic) =
|
||||
pylint::rules::import_self(alias, checker.module.qualified_name())
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -765,9 +786,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_bandit::rules::suspicious_imports(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::BannedApi) {
|
||||
if let Some(module) =
|
||||
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
||||
{
|
||||
if let Some(module) = helpers::resolve_imported_module_path(
|
||||
level,
|
||||
module,
|
||||
checker.module.qualified_name(),
|
||||
) {
|
||||
flake8_tidy_imports::rules::banned_api(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
@@ -794,9 +817,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BannedModuleLevelImports) {
|
||||
if let Some(module) =
|
||||
helpers::resolve_imported_module_path(level, module, checker.module_path)
|
||||
{
|
||||
if let Some(module) = helpers::resolve_imported_module_path(
|
||||
level,
|
||||
module,
|
||||
checker.module.qualified_name(),
|
||||
) {
|
||||
flake8_tidy_imports::rules::banned_module_level_imports(
|
||||
checker,
|
||||
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
|
||||
@@ -883,7 +908,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
stmt,
|
||||
level,
|
||||
module,
|
||||
checker.module_path,
|
||||
checker.module.qualified_name(),
|
||||
checker.settings.flake8_tidy_imports.ban_relative_imports,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -982,9 +1007,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ImportSelf) {
|
||||
if let Some(diagnostic) =
|
||||
pylint::rules::import_from_self(level, module, names, checker.module_path)
|
||||
{
|
||||
if let Some(diagnostic) = pylint::rules::import_from_self(
|
||||
level,
|
||||
module,
|
||||
names,
|
||||
checker.module.qualified_name(),
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -1050,6 +1078,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.enabled(Rule::SelfOrClsAssignment) {
|
||||
pylint::rules::self_or_cls_assignment(checker, target);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||
pylint::rules::global_statement(checker, id);
|
||||
@@ -1112,6 +1143,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::TooManyBooleanExpressions) {
|
||||
pylint::rules::too_many_boolean_expressions(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::IfStmtMinMax) {
|
||||
pylint::rules::if_stmt_min_max(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
@@ -1217,6 +1251,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ReadWholeFile) {
|
||||
refurb::rules::read_whole_file(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::WriteWholeFile) {
|
||||
refurb::rules::write_whole_file(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UselessWithLock) {
|
||||
pylint::rules::useless_with_lock(checker, with_stmt);
|
||||
}
|
||||
@@ -1257,6 +1294,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.any_enabled(&[
|
||||
Rule::EnumerateForLoop,
|
||||
Rule::IncorrectDictIterator,
|
||||
Rule::LoopIteratorMutation,
|
||||
Rule::UnnecessaryEnumerate,
|
||||
Rule::UnusedLoopControlVariable,
|
||||
Rule::YieldInForLoop,
|
||||
@@ -1398,6 +1436,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
|
||||
if checker.enabled(Rule::SelfOrClsAssignment) {
|
||||
for target in targets {
|
||||
pylint::rules::self_or_cls_assignment(checker, target);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::RedeclaredAssignedName) {
|
||||
pylint::rules::redeclared_assigned_name(checker, targets);
|
||||
}
|
||||
@@ -1467,6 +1510,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::TypeBivariance) {
|
||||
pylint::rules::type_bivariance(checker, value);
|
||||
}
|
||||
if checker.enabled(Rule::NonAugmentedAssignment) {
|
||||
pylint::rules::non_augmented_assignment(checker, assign);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::UnsortedDunderAll) {
|
||||
ruff::rules::sort_dunder_all_assign(checker, assign);
|
||||
}
|
||||
@@ -1532,6 +1578,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::SelfOrClsAssignment) {
|
||||
pylint::rules::self_or_cls_assignment(checker, target);
|
||||
}
|
||||
if checker.enabled(Rule::SelfAssigningVariable) {
|
||||
pylint::rules::self_annotated_assignment(checker, assign_stmt);
|
||||
}
|
||||
|
||||
@@ -33,4 +33,7 @@ pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryEscapedQuote) {
|
||||
flake8_quotes::rules::unnecessary_escaped_quote(checker, string_like);
|
||||
}
|
||||
if checker.enabled(Rule::AvoidableEscapedQuote) && checker.settings.flake8_quotes.avoid_escape {
|
||||
flake8_quotes::rules::avoidable_escaped_quote(checker, string_like);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,10 @@ impl AnnotationContext {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// If `__future__` annotations are enabled, then annotations are never evaluated
|
||||
// at runtime, so we can treat them as typing-only.
|
||||
if semantic.future_annotations() {
|
||||
// If `__future__` annotations are enabled or it's a stub file,
|
||||
// then annotations are never evaluated at runtime,
|
||||
// so we can treat them as typing-only.
|
||||
if semantic.future_annotations_or_stub() {
|
||||
return Self::TypingOnly;
|
||||
}
|
||||
|
||||
@@ -87,7 +88,7 @@ impl AnnotationContext {
|
||||
semantic,
|
||||
) {
|
||||
Self::RuntimeRequired
|
||||
} else if semantic.future_annotations() {
|
||||
} else if semantic.future_annotations_or_stub() {
|
||||
Self::TypingOnly
|
||||
} else {
|
||||
Self::RuntimeEvaluated
|
||||
|
||||
@@ -12,6 +12,8 @@ pub(crate) struct Visit<'a> {
|
||||
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<Snapshot>,
|
||||
/// N.B. This field should always be empty unless it's a stub file
|
||||
pub(crate) class_bases: Vec<(&'a Expr, Snapshot)>,
|
||||
}
|
||||
|
||||
impl Visit<'_> {
|
||||
@@ -22,6 +24,7 @@ impl Visit<'_> {
|
||||
&& self.type_param_definitions.is_empty()
|
||||
&& self.functions.is_empty()
|
||||
&& self.lambdas.is_empty()
|
||||
&& self.class_bases.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,15 +31,14 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use ruff_python_ast::{
|
||||
self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
|
||||
Suite, UnaryOp,
|
||||
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, FStringElement,
|
||||
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllDefinition, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
|
||||
};
|
||||
@@ -51,11 +50,11 @@ use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||
use ruff_python_codegen::{Generator, Stylist};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
||||
use ruff_python_semantic::analyze::{imports, typing, visibility};
|
||||
use ruff_python_semantic::analyze::{imports, typing};
|
||||
use ruff_python_semantic::{
|
||||
BindingFlags, BindingId, BindingKind, Exceptions, Export, FromImport, Globals, Import, Module,
|
||||
ModuleKind, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport,
|
||||
SubmoduleImport,
|
||||
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
|
||||
StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS};
|
||||
use ruff_source_file::{Locator, OneIndexed, SourceRow};
|
||||
@@ -111,7 +110,7 @@ pub(crate) struct Checker<'a> {
|
||||
/// The [`Path`] to the package containing the current file.
|
||||
package: Option<&'a Path>,
|
||||
/// The module representation of the current file (e.g., `foo.bar`).
|
||||
module_path: Option<&'a [String]>,
|
||||
module: Module<'a>,
|
||||
/// The [`PySourceType`] of the current file.
|
||||
pub(crate) source_type: PySourceType,
|
||||
/// The [`CellOffsets`] for the current file, if it's a Jupyter notebook.
|
||||
@@ -175,7 +174,7 @@ impl<'a> Checker<'a> {
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
module_path: module.path(),
|
||||
module,
|
||||
source_type,
|
||||
locator,
|
||||
stylist,
|
||||
@@ -572,6 +571,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
name,
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
@@ -691,9 +691,21 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
if let Some(globals) = Globals::from_body(body) {
|
||||
self.semantic.set_globals(globals);
|
||||
}
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::FunctionDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::ClassDef(
|
||||
class_def @ ast::StmtClassDef {
|
||||
name,
|
||||
body,
|
||||
arguments,
|
||||
decorator_list,
|
||||
@@ -712,7 +724,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
|
||||
if let Some(arguments) = arguments {
|
||||
self.semantic.flags |= SemanticModelFlags::CLASS_BASE;
|
||||
self.visit_arguments(arguments);
|
||||
self.semantic.flags -= SemanticModelFlags::CLASS_BASE;
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
@@ -732,6 +746,18 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
// Set the docstring state before visiting the class body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.visit_body(body);
|
||||
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Class scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::ClassDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
range: _,
|
||||
@@ -758,17 +784,13 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}) => {
|
||||
let mut handled_exceptions = Exceptions::empty();
|
||||
for type_ in extract_handled_exceptions(handlers) {
|
||||
if let Some(qualified_name) = self.semantic.resolve_qualified_name(type_) {
|
||||
match qualified_name.segments() {
|
||||
["", "NameError"] => {
|
||||
handled_exceptions |= Exceptions::NAME_ERROR;
|
||||
}
|
||||
["", "ModuleNotFoundError"] => {
|
||||
if let Some(builtins_name) = self.semantic.resolve_builtin_symbol(type_) {
|
||||
match builtins_name {
|
||||
"NameError" => handled_exceptions |= Exceptions::NAME_ERROR,
|
||||
"ModuleNotFoundError" => {
|
||||
handled_exceptions |= Exceptions::MODULE_NOT_FOUND_ERROR;
|
||||
}
|
||||
["", "ImportError"] => {
|
||||
handled_exceptions |= Exceptions::IMPORT_ERROR;
|
||||
}
|
||||
"ImportError" => handled_exceptions |= Exceptions::IMPORT_ERROR,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -888,35 +910,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
};
|
||||
|
||||
// Step 3: Clean-up
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::FunctionDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::ClassDef(ast::StmtClassDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Class scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::ClassDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Step 4: Analysis
|
||||
analyze::statement(stmt, self);
|
||||
@@ -935,10 +928,23 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
// Step 0: Pre-processing
|
||||
if self.source_type.is_stub()
|
||||
&& self.semantic.in_class_base()
|
||||
&& !self.semantic.in_deferred_class_base()
|
||||
{
|
||||
self.visit
|
||||
.class_bases
|
||||
.push((expr, self.semantic.snapshot()));
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.semantic.in_typing_literal()
|
||||
// `in_deferred_type_definition()` will only be `true` if we're now visiting the deferred nodes
|
||||
// after having already traversed the source tree once. If we're now visiting the deferred nodes,
|
||||
// we can't defer again, or we'll infinitely recurse!
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
&& self.semantic.future_annotations_or_stub()
|
||||
&& (self.semantic.in_annotation() || self.source_type.is_stub())
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
@@ -992,6 +998,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Store => self.handle_node_store(id, expr),
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
ExprContext::Invalid => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@@ -1115,7 +1122,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
]
|
||||
) {
|
||||
Some(typing::Callable::MypyExtension)
|
||||
} else if matches!(qualified_name.segments(), ["", "bool"]) {
|
||||
} else if matches!(qualified_name.segments(), ["" | "builtins", "bool"])
|
||||
{
|
||||
Some(typing::Callable::Bool)
|
||||
} else {
|
||||
None
|
||||
@@ -1580,6 +1588,15 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
.push((bound, self.semantic.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
|
||||
let snapshot = self.semantic.flags;
|
||||
if f_string_element.is_expression() {
|
||||
self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD;
|
||||
}
|
||||
visitor::walk_f_string_element(self, f_string_element);
|
||||
self.semantic.flags = snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -1893,7 +1910,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
{
|
||||
let (all_names, all_flags) =
|
||||
extract_all_names(parent, |name| self.semantic.is_builtin(name));
|
||||
extract_all_names(parent, |name| self.semantic.has_builtin_binding(name));
|
||||
|
||||
if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) {
|
||||
flags |= BindingFlags::INVALID_ALL_OBJECT;
|
||||
@@ -1956,6 +1973,52 @@ impl<'a> Checker<'a> {
|
||||
scope.add(id, binding_id);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all class bases that were deferred.
|
||||
///
|
||||
/// This method should only be relevant in stub files, where forward references are
|
||||
/// legal in class bases. For other kinds of Python files, using a forward reference
|
||||
/// in a class base is never legal, so `self.visit.class_bases` should always be empty.
|
||||
///
|
||||
/// For example, in a stub file:
|
||||
/// ```python
|
||||
/// class Foo(list[Bar]): ... # <-- `Bar` is a forward reference in a class base
|
||||
/// class Bar: ...
|
||||
/// ```
|
||||
fn visit_deferred_class_bases(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
let deferred_bases = std::mem::take(&mut self.visit.class_bases);
|
||||
debug_assert!(
|
||||
self.source_type.is_stub() || deferred_bases.is_empty(),
|
||||
"Class bases should never be deferred outside of stub files"
|
||||
);
|
||||
for (expr, snapshot) in deferred_bases {
|
||||
self.semantic.restore(snapshot);
|
||||
// Set this flag to avoid infinite recursion, or we'll just defer it again:
|
||||
self.semantic.flags |= SemanticModelFlags::DEFERRED_CLASS_BASE;
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all "future type definitions".
|
||||
///
|
||||
/// A "future type definition" is a type definition where [PEP 563] semantics
|
||||
/// apply (i.e., an annotation in a module that has `from __future__ import annotations`
|
||||
/// at the top of the file, or an annotation in a stub file). These type definitions
|
||||
/// support forward references, so they are deferred on initial traversal
|
||||
/// of the source tree.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// def foo() -> Bar: # <-- return annotation is a "future type definition"
|
||||
/// return Bar()
|
||||
///
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 563]: https://peps.python.org/pep-0563/
|
||||
fn visit_deferred_future_type_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.future_type_definitions.is_empty() {
|
||||
@@ -1963,6 +2026,14 @@ impl<'a> Checker<'a> {
|
||||
for (expr, snapshot) in type_definitions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
// Type definitions should only be considered "`__future__` type definitions"
|
||||
// if they are annotations in a module where `from __future__ import
|
||||
// annotations` is active, or they are type definitions in a stub file.
|
||||
debug_assert!(
|
||||
self.semantic.future_annotations_or_stub()
|
||||
&& (self.source_type.is_stub() || self.semantic.in_annotation())
|
||||
);
|
||||
|
||||
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION
|
||||
| SemanticModelFlags::FUTURE_TYPE_DEFINITION;
|
||||
self.visit_expr(expr);
|
||||
@@ -1971,6 +2042,19 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all [type parameter definitions].
|
||||
///
|
||||
/// Type parameters natively support forward references,
|
||||
/// so are always deferred during initial traversal of the source tree.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// class Foo[T: Bar]: pass # <-- Forward reference used in definition of type parameter `T`
|
||||
/// type X[T: Bar] = Foo[T] # <-- Ditto
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
///
|
||||
/// [type parameter definitions]: https://docs.python.org/3/reference/executionmodel.html#annotation-scopes
|
||||
fn visit_deferred_type_param_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.type_param_definitions.is_empty() {
|
||||
@@ -1986,6 +2070,17 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all "string type definitions",
|
||||
/// i.e., type definitions that are enclosed within quotes so as to allow
|
||||
/// the type definition to use forward references.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// def foo() -> "Bar": # <-- return annotation is a "string type definition"
|
||||
/// return Bar()
|
||||
///
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.string_type_definitions.is_empty() {
|
||||
@@ -1998,7 +2093,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_annotation() && self.semantic.future_annotations() {
|
||||
if self.semantic.in_annotation() && self.semantic.future_annotations_or_stub() {
|
||||
if self.enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
}
|
||||
@@ -2034,6 +2129,11 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all function bodies.
|
||||
///
|
||||
/// Function bodies are always deferred on initial traversal of the source tree,
|
||||
/// as the body of a function may validly contain references to global-scope symbols
|
||||
/// that were not yet defined at the point when the function was defined.
|
||||
fn visit_deferred_functions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.functions.is_empty() {
|
||||
@@ -2057,8 +2157,9 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// Visit all deferred lambdas. Returns a list of snapshots, such that the caller can restore
|
||||
/// the semantic model to the state it was in before visiting the deferred lambdas.
|
||||
/// After initial traversal of the source tree has been completed,
|
||||
/// visit all lambdas. Lambdas are deferred during the initial traversal
|
||||
/// for the same reason as function bodies.
|
||||
fn visit_deferred_lambdas(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.lambdas.is_empty() {
|
||||
@@ -2084,10 +2185,12 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// Recursively visit all deferred AST nodes, including lambdas, functions, and type
|
||||
/// annotations.
|
||||
/// After initial traversal of the source tree has been completed,
|
||||
/// recursively visit all AST nodes that were deferred on the first pass.
|
||||
/// This includes lambdas, functions, type parameters, and type annotations.
|
||||
fn visit_deferred(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
while !self.visit.is_empty() {
|
||||
self.visit_deferred_class_bases();
|
||||
self.visit_deferred_functions();
|
||||
self.visit_deferred_type_param_definitions();
|
||||
self.visit_deferred_lambdas();
|
||||
@@ -2100,45 +2203,54 @@ impl<'a> Checker<'a> {
|
||||
fn visit_exports(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
|
||||
let exports: Vec<DunderAllName> = self
|
||||
let definitions: Vec<DunderAllDefinition> = self
|
||||
.semantic
|
||||
.global_scope()
|
||||
.get_all("__all__")
|
||||
.map(|binding_id| &self.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
|
||||
BindingKind::Export(Export { names }) => {
|
||||
Some(DunderAllDefinition::new(binding.range(), names.to_vec()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
for export in exports {
|
||||
let (name, range) = (export.name(), export.range());
|
||||
if let Some(binding_id) = self.semantic.global_scope().get(name) {
|
||||
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
} else {
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
for definition in definitions {
|
||||
for export in definition.names() {
|
||||
let (name, range) = (export.name(), export.range());
|
||||
if let Some(binding_id) = self.semantic.global_scope().get(name) {
|
||||
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
} else {
|
||||
if self.enabled(Rule::UndefinedExport) {
|
||||
if !self.path.ends_with("__init__.py") {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
)
|
||||
.with_parent(definition.start()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if self.enabled(Rule::UndefinedExport) {
|
||||
if !self.path.ends_with("__init__.py") {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
)
|
||||
.with_parent(definition.start()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2171,10 +2283,15 @@ pub(crate) fn check_ast(
|
||||
} else {
|
||||
ModuleKind::Module
|
||||
},
|
||||
source: if let Some(module_path) = module_path.as_ref() {
|
||||
visibility::ModuleSource::Path(module_path)
|
||||
name: if let Some(module_path) = &module_path {
|
||||
module_path.last().map(String::as_str)
|
||||
} else {
|
||||
visibility::ModuleSource::File(path)
|
||||
path.file_stem().and_then(std::ffi::OsStr::to_str)
|
||||
},
|
||||
source: if let Some(module_path) = module_path.as_ref() {
|
||||
ModuleSource::Path(module_path)
|
||||
} else {
|
||||
ModuleSource::File(path)
|
||||
},
|
||||
python_ast,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::noqa;
|
||||
use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping};
|
||||
use crate::registry::{AsRule, Rule, RuleSet};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::pygrep_hooks;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
@@ -111,6 +112,7 @@ pub(crate) fn check_noqa(
|
||||
FileExemption::All => true,
|
||||
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
|
||||
})
|
||||
&& !per_file_ignores.contains(Rule::UnusedNOQA)
|
||||
{
|
||||
for line in noqa_directives.lines() {
|
||||
match &line.directive {
|
||||
@@ -129,7 +131,7 @@ pub(crate) fn check_noqa(
|
||||
let mut unknown_codes = vec![];
|
||||
let mut unmatched_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
let mut self_ignore = per_file_ignores.contains(Rule::UnusedNOQA);
|
||||
let mut self_ignore = false;
|
||||
for code in directive.codes() {
|
||||
let code = get_redirect_target(code).unwrap_or(code);
|
||||
if Rule::UnusedNOQA.noqa_code() == code {
|
||||
@@ -202,6 +204,10 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketNOQA) {
|
||||
pygrep_hooks::rules::blanket_noqa(diagnostics, &noqa_directives, locator);
|
||||
}
|
||||
|
||||
ignored_diagnostics.sort_unstable();
|
||||
ignored_diagnostics
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::BlankLinesChecker;
|
||||
use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
@@ -45,10 +45,6 @@ pub(crate) fn check_tokens(
|
||||
.check_lines(tokens, &mut diagnostics);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketNOQA) {
|
||||
pygrep_hooks::rules::blanket_noqa(&mut diagnostics, indexer, locator);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketTypeIgnore) {
|
||||
pygrep_hooks::rules::blanket_type_ignore(&mut diagnostics, indexer, locator);
|
||||
}
|
||||
@@ -122,10 +118,6 @@ pub(crate) fn check_tokens(
|
||||
);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape {
|
||||
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MultiLineImplicitStringConcatenation,
|
||||
|
||||
@@ -225,12 +225,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(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, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
|
||||
(Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
|
||||
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
|
||||
(Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall),
|
||||
(Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit),
|
||||
(Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit),
|
||||
@@ -241,8 +241,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E0237") => (RuleGroup::Stable, rules::pylint::rules::NonSlotAssignment),
|
||||
(Pylint, "E0241") => (RuleGroup::Stable, rules::pylint::rules::DuplicateBases),
|
||||
(Pylint, "E0302") => (RuleGroup::Stable, rules::pylint::rules::UnexpectedSpecialMethodSignature),
|
||||
(Pylint, "E0303") => (RuleGroup::Preview, rules::pylint::rules::InvalidLengthReturnType),
|
||||
(Pylint, "E0304") => (RuleGroup::Preview, rules::pylint::rules::InvalidBoolReturnType),
|
||||
(Pylint, "E0305") => (RuleGroup::Preview, rules::pylint::rules::InvalidIndexReturnType),
|
||||
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
|
||||
(Pylint, "E0308") => (RuleGroup::Preview, rules::pylint::rules::InvalidBytesReturnType),
|
||||
(Pylint, "E0309") => (RuleGroup::Preview, rules::pylint::rules::InvalidHashReturnType),
|
||||
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
||||
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
||||
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
|
||||
@@ -272,6 +276,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0203") => (RuleGroup::Preview, rules::pylint::rules::NoStaticmethodDecorator),
|
||||
(Pylint, "R0206") => (RuleGroup::Stable, rules::pylint::rules::PropertyWithParameters),
|
||||
(Pylint, "R0402") => (RuleGroup::Stable, rules::pylint::rules::ManualFromImport),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "R0911") => (RuleGroup::Stable, rules::pylint::rules::TooManyReturnStatements),
|
||||
(Pylint, "R0912") => (RuleGroup::Stable, rules::pylint::rules::TooManyBranches),
|
||||
(Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments),
|
||||
@@ -282,31 +287,35 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks),
|
||||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1730") => (RuleGroup::Preview, rules::pylint::rules::IfStmtMinMax),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R2044") => (RuleGroup::Preview, rules::pylint::rules::EmptyComment),
|
||||
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
|
||||
(Pylint, "R6104") => (RuleGroup::Preview, rules::pylint::rules::NonAugmentedAssignment),
|
||||
(Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
|
||||
(Pylint, "W0117") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
||||
(Pylint, "W0128") => (RuleGroup::Preview, rules::pylint::rules::RedeclaredAssignedName),
|
||||
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
|
||||
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
|
||||
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),
|
||||
(Pylint, "W0211") => (RuleGroup::Preview, rules::pylint::rules::BadStaticmethodArgument),
|
||||
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
|
||||
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
|
||||
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
|
||||
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
|
||||
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0642") => (RuleGroup::Preview, rules::pylint::rules::SelfOrClsAssignment),
|
||||
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
|
||||
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
@@ -316,7 +325,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
@@ -376,6 +384,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
|
||||
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
|
||||
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
|
||||
|
||||
// flake8-blind-except
|
||||
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
||||
@@ -546,6 +555,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
|
||||
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
|
||||
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
|
||||
(Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum),
|
||||
|
||||
// pydocstyle
|
||||
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
|
||||
@@ -957,6 +967,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "026") => (RuleGroup::Preview, rules::ruff::rules::DefaultFactoryKwarg),
|
||||
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
|
||||
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
||||
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
|
||||
#[cfg(feature = "test-rules")]
|
||||
@@ -1034,7 +1045,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// refurb
|
||||
(Refurb, "101") => (RuleGroup::Preview, rules::refurb::rules::ReadWholeFile),
|
||||
(Refurb, "103") => (RuleGroup::Preview, rules::refurb::rules::WriteWholeFile),
|
||||
(Refurb, "105") => (RuleGroup::Preview, rules::refurb::rules::PrintEmptyString),
|
||||
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
@@ -1062,6 +1075,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "180") => (RuleGroup::Preview, rules::refurb::rules::MetaClassABCMeta),
|
||||
(Refurb, "181") => (RuleGroup::Preview, rules::refurb::rules::HashlibDigestHex),
|
||||
(Refurb, "187") => (RuleGroup::Preview, rules::refurb::rules::ListReverseCopy),
|
||||
(Refurb, "192") => (RuleGroup::Preview, rules::refurb::rules::SortedMinMax),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Stable, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
|
||||
@@ -1,36 +1,46 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::registry::RuleSet;
|
||||
use crate::settings::types::CompiledPerFileIgnoreList;
|
||||
|
||||
/// Create a set with codes matching the pattern/code pairs.
|
||||
pub(crate) fn ignores_from_path(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, RuleSet)],
|
||||
) -> RuleSet {
|
||||
pub(crate) fn ignores_from_path(path: &Path, ignore_list: &CompiledPerFileIgnoreList) -> RuleSet {
|
||||
let file_name = path.file_name().expect("Unable to parse filename");
|
||||
pattern_code_pairs
|
||||
ignore_list
|
||||
.iter()
|
||||
.filter_map(|(absolute, basename, rules)| {
|
||||
if basename.is_match(file_name) {
|
||||
.filter_map(|entry| {
|
||||
if entry.basename_matcher.is_match(file_name) {
|
||||
if entry.negated { None } else {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
|
||||
path,
|
||||
entry.basename_matcher.glob().regex(),
|
||||
entry.rules
|
||||
);
|
||||
Some(&entry.rules)
|
||||
}
|
||||
} else if entry.absolute_matcher.is_match(path) {
|
||||
if entry.negated { None } else {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
|
||||
path,
|
||||
entry.absolute_matcher.glob().regex(),
|
||||
entry.rules
|
||||
);
|
||||
Some(&entry.rules)
|
||||
}
|
||||
} else if entry.negated {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
|
||||
"Adding per-file ignores for {:?} due to negated pattern matching neither {:?} nor {:?}: {:?}",
|
||||
path,
|
||||
basename.glob().regex(),
|
||||
rules
|
||||
entry.basename_matcher.glob().regex(),
|
||||
entry.absolute_matcher.glob().regex(),
|
||||
entry.rules
|
||||
);
|
||||
Some(rules)
|
||||
} else if absolute.is_match(path) {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
|
||||
path,
|
||||
absolute.glob().regex(),
|
||||
rules
|
||||
);
|
||||
Some(rules)
|
||||
Some(&entry.rules)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -229,6 +229,31 @@ impl<'a> Importer<'a> {
|
||||
.map_or_else(|| self.import_symbol(symbol, at, None, semantic), Ok)
|
||||
}
|
||||
|
||||
/// For a given builtin symbol, determine whether an [`Edit`] is necessary to make the symbol
|
||||
/// available in the current scope. For example, if `zip` has been overridden in the relevant
|
||||
/// scope, the `builtins` module will need to be imported in order for a `Fix` to reference
|
||||
/// `zip`; but otherwise, that won't be necessary.
|
||||
///
|
||||
/// Returns a two-item tuple. The first item is either `Some(Edit)` (indicating) that an
|
||||
/// edit is necessary to make the symbol available, or `None`, indicating that the symbol has
|
||||
/// not been overridden in the current scope. The second item in the tuple is the bound name
|
||||
/// of the symbol.
|
||||
///
|
||||
/// Attempts to reuse existing imports when possible.
|
||||
pub(crate) fn get_or_import_builtin_symbol(
|
||||
&self,
|
||||
symbol: &str,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Result<(Option<Edit>, String), ResolutionError> {
|
||||
if semantic.has_builtin_binding(symbol) {
|
||||
return Ok((None, symbol.to_string()));
|
||||
}
|
||||
let (import_edit, binding) =
|
||||
self.get_or_import_symbol(&ImportRequest::import("builtins", symbol), at, semantic)?;
|
||||
Ok((Some(import_edit), binding))
|
||||
}
|
||||
|
||||
/// Return the [`ImportedName`] to for existing symbol, if it's present in the given [`SemanticModel`].
|
||||
fn find_symbol(
|
||||
symbol: &ImportRequest,
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
//! Extract docstrings via tokenization.
|
||||
//!
|
||||
//! See: <https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29>
|
||||
//!
|
||||
//! TODO(charlie): Consolidate with the existing AST-based docstring extraction.
|
||||
|
||||
use ruff_python_parser::Tok;
|
||||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
enum State {
|
||||
// Start of the module: first string gets marked as a docstring.
|
||||
#[default]
|
||||
ExpectModuleDocstring,
|
||||
// After seeing a class definition, we're waiting for the block colon (and do bracket
|
||||
// counting).
|
||||
ExpectClassColon,
|
||||
// After seeing the block colon in a class definition, we expect a docstring.
|
||||
ExpectClassDocstring,
|
||||
// Same as ExpectClassColon, but for function definitions.
|
||||
ExpectFunctionColon,
|
||||
// Same as ExpectClassDocstring, but for function definitions.
|
||||
ExpectFunctionDocstring,
|
||||
// Skip tokens until we observe a `class` or `def`.
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct StateMachine {
|
||||
state: State,
|
||||
bracket_count: usize,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub(crate) fn consume(&mut self, tok: &Tok) -> bool {
|
||||
match tok {
|
||||
Tok::NonLogicalNewline
|
||||
| Tok::Newline
|
||||
| Tok::Indent
|
||||
| Tok::Dedent
|
||||
| Tok::Comment(..) => false,
|
||||
|
||||
Tok::String { .. } => {
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Tok::Class => {
|
||||
self.state = State::ExpectClassColon;
|
||||
self.bracket_count = 0;
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Def => {
|
||||
self.state = State::ExpectFunctionColon;
|
||||
self.bracket_count = 0;
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Colon => {
|
||||
if self.bracket_count == 0 {
|
||||
if matches!(self.state, State::ExpectClassColon) {
|
||||
self.state = State::ExpectClassDocstring;
|
||||
} else if matches!(self.state, State::ExpectFunctionColon) {
|
||||
self.state = State::ExpectFunctionDocstring;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Lpar | Tok::Lbrace | Tok::Lsqb => {
|
||||
self.bracket_count = self.bracket_count.saturating_add(1);
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Tok::Rpar | Tok::Rbrace | Tok::Rsqb => {
|
||||
self.bracket_count = self.bracket_count.saturating_sub(1);
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
_ => {
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
| State::ExpectClassDocstring
|
||||
| State::ExpectFunctionDocstring
|
||||
) {
|
||||
self.state = State::Other;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod docstring_detection;
|
||||
@@ -24,7 +24,6 @@ mod docstrings;
|
||||
mod fix;
|
||||
pub mod fs;
|
||||
mod importer;
|
||||
mod lex;
|
||||
pub mod line_width;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
|
||||
@@ -265,7 +265,13 @@ pub fn check_path(
|
||||
}
|
||||
|
||||
// Ignore diagnostics based on per-file-ignores.
|
||||
let per_file_ignores = if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
|
||||
let per_file_ignores = if (!diagnostics.is_empty()
|
||||
|| settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_noqa()))
|
||||
&& !settings.per_file_ignores.is_empty()
|
||||
{
|
||||
fs::ignores_from_path(path, &settings.per_file_ignores)
|
||||
} else {
|
||||
RuleSet::empty()
|
||||
|
||||
@@ -194,7 +194,7 @@ impl DisplayParseError {
|
||||
// Translate the byte offset to a location in the originating source.
|
||||
let location =
|
||||
if let Some(jupyter_index) = source_kind.as_ipy_notebook().map(Notebook::index) {
|
||||
let source_location = source_code.source_location(error.offset);
|
||||
let source_location = source_code.source_location(error.location.start());
|
||||
|
||||
ErrorLocation::Cell(
|
||||
jupyter_index
|
||||
@@ -208,7 +208,7 @@ impl DisplayParseError {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
ErrorLocation::File(source_code.source_location(error.offset))
|
||||
ErrorLocation::File(source_code.source_location(error.location.start()))
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -275,27 +275,7 @@ impl<'a> DisplayParseErrorType<'a> {
|
||||
|
||||
impl Display for DisplayParseErrorType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0 {
|
||||
ParseErrorType::Eof => write!(f, "Expected token but reached end of file."),
|
||||
ParseErrorType::ExtraToken(ref tok) => write!(
|
||||
f,
|
||||
"Got extraneous token: {tok}",
|
||||
tok = TruncateAtNewline(&tok)
|
||||
),
|
||||
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
|
||||
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
|
||||
if let Some(expected) = expected.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
"Expected '{expected}', but got {tok}",
|
||||
tok = TruncateAtNewline(&tok)
|
||||
)
|
||||
} else {
|
||||
write!(f, "Unexpected token {tok}", tok = TruncateAtNewline(&tok))
|
||||
}
|
||||
}
|
||||
ParseErrorType::Lexical(ref error) => write!(f, "{error}"),
|
||||
}
|
||||
write!(f, "{}", TruncateAtNewline(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ impl<'a> Directive<'a> {
|
||||
|
||||
/// Lex an individual rule code (e.g., `F401`).
|
||||
#[inline]
|
||||
fn lex_code(line: &str) -> Option<&str> {
|
||||
pub(crate) fn lex_code(line: &str) -> Option<&str> {
|
||||
// Extract, e.g., the `F` in `F401`.
|
||||
let prefix = line.chars().take_while(char::is_ascii_uppercase).count();
|
||||
// Extract, e.g., the `401` in `F401`.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user