Compare commits

..

1 Commits

Author SHA1 Message Date
Zanie
69fc2caa31 Add CI job to auto-update pre-commit dependencies weekly 2023-11-01 12:06:14 -05:00
181 changed files with 2987 additions and 10874 deletions

View File

@@ -184,11 +184,7 @@ jobs:
- cargo-test-linux
- 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.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
}}
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
@@ -213,7 +209,7 @@ jobs:
run: |
pip install ./python/ruff-ecosystem
- name: Run `ruff check` stable ecosystem check
- name: Run `ruff check` ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
@@ -222,30 +218,13 @@ jobs:
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check
cat ecosystem-result-check-stable > $GITHUB_STEP_SUMMARY
echo "### Linter (stable)" > ecosystem-result
cat ecosystem-result-check-stable >> ecosystem-result
cat ecosystem-result-check > $GITHUB_STEP_SUMMARY
cat ecosystem-result-check > ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff check` preview ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
cat ecosystem-result-check-preview > $GITHUB_STEP_SUMMARY
echo "### Linter (preview)" >> ecosystem-result
cat ecosystem-result-check-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` stable ecosystem check
- name: Run `ruff format` ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
@@ -254,28 +233,10 @@ jobs:
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format
cat ecosystem-result-format-stable > $GITHUB_STEP_SUMMARY
echo "### Formatter (stable)" >> ecosystem-result
cat ecosystem-result-format-stable >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` preview ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
cat ecosystem-result-format-preview > $GITHUB_STEP_SUMMARY
echo "### Formatter (preview)" >> ecosystem-result
cat ecosystem-result-format-preview >> ecosystem-result
echo "" >> ecosystem-result
cat ecosystem-result-format > $GITHUB_STEP_SUMMARY
cat ecosystem-result-format >> ecosystem-result
- name: Export pull request number
run: |

View File

@@ -1,4 +1,4 @@
name: Ecosystem check comment
name: PR Check Comment
on:
workflow_run:
@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download pull request number
name: Download PR Number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Parse pull request number
- name: Extract PR Number
id: pr-number
run: |
if [[ -f pr-number ]]
@@ -33,7 +33,7 @@ jobs:
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download ecosystem results"
name: "Download Ecosystem Result"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
@@ -44,15 +44,13 @@ jobs:
workflow_conclusion: completed
if_no_artifact_found: ignore
- name: Generate comment content
- name: Generate Comment
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
run: |
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment ecosystem -->' >> comment.txt
echo '## PR Check Results' >> comment.txt
echo '## `ruff-ecosystem` results' >> comment.txt
echo "### Ecosystem" >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
@@ -60,14 +58,14 @@ jobs:
cat comment.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Find existing comment
- name: Find Comment
uses: peter-evans/find-comment@v2
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ecosystem -->"
body-includes: PR Check Results
- name: Create or update comment
if: steps.find-comment.outcome == 'success'

36
.github/workflows/pre-commit.yaml vendored Normal file
View File

@@ -0,0 +1,36 @@
# Until Dependabot support is released https://github.com/dependabot/dependabot-core/issues/1524
name: Pre-commit update
on:
# every week on monday
schedule:
- cron: "0 0 * * 1"
workflow_dispatch:
permissions:
pull-requests: write
jobs:
upgrade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run autoupdate
run: |
pre-commit autoupdate
- name: Commit and push
run: |
git add ".pre-commit-config.yaml"
git commit -m "Upgrade pre-commit dependencies"
git push origin upgrade/pre-commit
- name: Open pull request
run: |
gh pr create --fill

View File

@@ -48,6 +48,7 @@ jobs:
args: --out dist
- name: "Test sdist"
run: |
rustup default $(cat rust-toolchain)
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
ruff --help
python -m ruff --help

View File

@@ -13,12 +13,12 @@ exclude: |
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.15
rev: v0.12.1
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.17
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
@@ -26,22 +26,16 @@ repos:
- mdformat-admon
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
docs/formatter/black.md
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.37.0
rev: v0.33.0
hooks:
- id: markdownlint-fix
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/crate-ci/typos
rev: v1.16.22
rev: v1.14.12
hooks:
- id: typos
@@ -53,13 +47,10 @@ repos:
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: ruff-format
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
name: ruff
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -68,9 +59,15 @@ repos:
crates/ruff_python_formatter/resources/.*
)$
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.3
rev: v3.0.0
hooks:
- id: prettier
types: [yaml]

View File

@@ -1,66 +1,5 @@
# Changelog
## 0.1.4
### Preview features
- \[`flake8-trio`\] Implement `timeout-without-await` (`TRIO001`) ([#8439](https://github.com/astral-sh/ruff/pull/8439))
- \[`numpy`\] Implement NumPy 2.0 migration rule (`NPY200`) ([#7702](https://github.com/astral-sh/ruff/pull/7702))
- \[`pylint`\] Implement `bad-open-mode` (`W1501`) ([#8294](https://github.com/astral-sh/ruff/pull/8294))
- \[`pylint`\] Implement `import-outside-toplevel` (`C0415`) rule ([#5180](https://github.com/astral-sh/ruff/pull/5180))
- \[`pylint`\] Implement `useless-with-lock` (`W2101`) ([#8321](https://github.com/astral-sh/ruff/pull/8321))
- \[`pyupgrade`\] Implement `timeout-error-alias` (`UP041`) ([#8476](https://github.com/astral-sh/ruff/pull/8476))
- \[`refurb`\] Implement `isinstance-type-none` (`FURB168`) ([#8308](https://github.com/astral-sh/ruff/pull/8308))
- Detect confusable Unicode-to-Unicode units in `RUF001`, `RUF002`, and `RUF003` ([#4430](https://github.com/astral-sh/ruff/pull/4430))
- Add newline after module docstrings in preview style ([#8283](https://github.com/astral-sh/ruff/pull/8283))
### Formatter
- Add a note on line-too-long to the formatter docs ([#8314](https://github.com/astral-sh/ruff/pull/8314))
- Preserve trailing statement semicolons when using `fmt: skip` ([#8273](https://github.com/astral-sh/ruff/pull/8273))
- Preserve trailing semicolons when using `fmt: off` ([#8275](https://github.com/astral-sh/ruff/pull/8275))
- Avoid duplicating linter-formatter compatibility warnings ([#8292](https://github.com/astral-sh/ruff/pull/8292))
- Avoid inserting a newline after function docstrings ([#8375](https://github.com/astral-sh/ruff/pull/8375))
- Insert newline between docstring and following own line comment ([#8216](https://github.com/astral-sh/ruff/pull/8216))
- Split tuples in return positions by comma first ([#8280](https://github.com/astral-sh/ruff/pull/8280))
- Avoid treating byte strings as docstrings ([#8350](https://github.com/astral-sh/ruff/pull/8350))
- Add `--line-length` option to `format` command ([#8363](https://github.com/astral-sh/ruff/pull/8363))
- Avoid parenthesizing unsplittable because of comments ([#8431](https://github.com/astral-sh/ruff/pull/8431))
### CLI
- Add `--output-format` to `ruff rule` and `ruff linter` ([#8203](https://github.com/astral-sh/ruff/pull/8203))
### Bug fixes
- Respect `--force-exclude` in `lint.exclude` and `format.exclude` ([#8393](https://github.com/astral-sh/ruff/pull/8393))
- Respect `--extend-per-file-ignores` on the CLI ([#8329](https://github.com/astral-sh/ruff/pull/8329))
- Extend `bad-dunder-method-name` to permit `__index__` ([#8300](https://github.com/astral-sh/ruff/pull/8300))
- Fix panic with 8 in octal escape ([#8356](https://github.com/astral-sh/ruff/pull/8356))
- Avoid raising `D300` when both triple quote styles are present ([#8462](https://github.com/astral-sh/ruff/pull/8462))
- Consider unterminated f-strings in `FStringRanges` ([#8154](https://github.com/astral-sh/ruff/pull/8154))
- Avoid including literal `shell=True` for truthy, non-`True` diagnostics ([#8359](https://github.com/astral-sh/ruff/pull/8359))
- Avoid triggering single-element test for starred expressions ([#8433](https://github.com/astral-sh/ruff/pull/8433))
- Detect and ignore Jupyter automagics ([#8398](https://github.com/astral-sh/ruff/pull/8398))
- Fix invalid E231 error with f-strings ([#8369](https://github.com/astral-sh/ruff/pull/8369))
- Avoid triggering `NamedTuple` rewrite with starred annotation ([#8434](https://github.com/astral-sh/ruff/pull/8434))
- Avoid un-setting bracket flag in logical lines ([#8380](https://github.com/astral-sh/ruff/pull/8380))
- Place 'r' prefix before 'f' for raw format strings ([#8464](https://github.com/astral-sh/ruff/pull/8464))
- Remove trailing periods from NumPy 2.0 code actions ([#8475](https://github.com/astral-sh/ruff/pull/8475))
- Fix bug where `PLE1307` was raised when formatting `%c` with characters ([#8407](https://github.com/astral-sh/ruff/pull/8407))
- Remove unicode flag from comparable ([#8440](https://github.com/astral-sh/ruff/pull/8440))
- Improve B015 message ([#8295](https://github.com/astral-sh/ruff/pull/8295))
- Use `fixedOverflowWidgets` for playground popover ([#8458](https://github.com/astral-sh/ruff/pull/8458))
- Mark `byte_bounds` as a non-backwards-compatible NumPy 2.0 change ([#8474](https://github.com/astral-sh/ruff/pull/8474))
### Internals
- Add a dedicated cache directory per Ruff version ([#8333](https://github.com/astral-sh/ruff/pull/8333))
- Allow selective caching for `--fix` and `--diff` ([#8316](https://github.com/astral-sh/ruff/pull/8316))
- Improve performance of comment parsing ([#8193](https://github.com/astral-sh/ruff/pull/8193))
- Improve performance of string parsing ([#8227](https://github.com/astral-sh/ruff/pull/8227))
- Use a dedicated sort key for isort import sorting ([#7963](https://github.com/astral-sh/ruff/pull/7963))
## 0.1.3
This release includes a variety of improvements to the Ruff formatter, removing several known and

View File

@@ -72,7 +72,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<charlie.r.marsh@gmail.com>.
charlie.r.marsh@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -337,15 +337,16 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
## Ecosystem CI
GitHub Actions will run your changes against a number of real-world projects from GitHub and
report on any linter or formatter differences. You can also run those checks locally via:
report on any diagnostic differences. You can also run those checks locally via:
```shell
pip install -e ./python/ruff-ecosystem
ruff-ecosystem check ruff "./target/debug/ruff"
ruff-ecosystem format ruff "./target/debug/ruff"
python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
```
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
## Benchmarking and Profiling

82
Cargo.lock generated
View File

@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bstr"
@@ -383,7 +383,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
checksum = "d680ccd1eedd2dd7c7a3649a78c7d06e0f16b191b30d81cc58e7bc906488d344"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
checksum = "58b48b6c8e890d7d4ad0ed85e9ab4949bf7023198c006000ef6338ba84cf5b71"
dependencies = [
"codspeed",
"colored",
@@ -608,7 +608,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.4"
version = "0.1.3"
dependencies = [
"anyhow",
"clap",
@@ -1129,7 +1129,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -1440,7 +1440,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@@ -1707,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2060,14 +2060,14 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.4"
version = "0.1.3"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
"argfile",
"assert_cmd",
"bincode",
"bitflags 2.4.1",
"bitflags 2.4.0",
"cachedir",
"chrono",
"clap",
@@ -2196,12 +2196,12 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.4"
version = "0.1.3"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"chrono",
"clap",
"colored",
@@ -2267,7 +2267,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2293,7 +2293,7 @@ dependencies = [
name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2325,7 +2325,7 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"clap",
"countme",
"insta",
@@ -2369,7 +2369,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"hexf-parse",
"is-macro",
"itertools 0.11.0",
@@ -2383,7 +2383,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2413,7 +2413,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"is-macro",
"ruff_index",
"ruff_python_ast",
@@ -2447,7 +2447,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.4"
version = "0.1.3"
dependencies = [
"anyhow",
"clap",
@@ -2563,7 +2563,7 @@ version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
@@ -2682,9 +2682,9 @@ dependencies = [
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
dependencies = [
"js-sys",
"serde",
@@ -2699,7 +2699,7 @@ checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2715,9 +2715,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
@@ -2761,7 +2761,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2856,7 +2856,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2872,9 +2872,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@@ -2961,7 +2961,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2973,7 +2973,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"test-case-core",
]
@@ -2994,7 +2994,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3131,7 +3131,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3349,7 +3349,7 @@ checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3443,7 +3443,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"wasm-bindgen-shared",
]
@@ -3477,7 +3477,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -13,7 +13,7 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.4.1" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.7", features = ["derive"] }
colored = { version = "2.0.0" }
@@ -35,14 +35,14 @@ regex = { version = "1.10.2" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.190", features = ["derive"] }
serde_json = { version = "1.0.108" }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.39" }
syn = { version = "2.0.38" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.50" }
toml = { version = "0.7.8" }
@@ -56,7 +56,7 @@ uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "
wsl = { version = "0.1.0" }
[profile.release]
lto = "thin"
lto = "fat"
codegen-units = 1
[profile.dev.package.insta]

25
LICENSE
View File

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

View File

@@ -148,9 +148,10 @@ ruff format @arguments.txt # Format using an input file, treating its
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
```yaml
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.4
rev: v0.1.3
hooks:
# Run the Ruff linter.
- id: ruff
@@ -313,7 +314,6 @@ quality tools, including:
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-trio](https://pypi.org/project/flake8-trio/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
@@ -415,7 +415,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PDM](https://github.com/pdm-project/pdm)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Pillow](https://github.com/python-pillow/Pillow)
- [Poetry](https://github.com/python-poetry/poetry)
- [Polars](https://github.com/pola-rs/polars)
- [PostHog](https://github.com/PostHog/posthog)
@@ -424,7 +423,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint)
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
- [Reflex](https://github.com/reflex-dev/reflex)
- [Rippling](https://rippling.com)
- [Robyn](https://github.com/sansyrox/robyn)

View File

@@ -1,6 +1,5 @@
[files]
# https://github.com/crate-ci/typos/issues/868
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
extend-exclude = ["resources", "snapshots"]
[default.extend-words]
hel = "hel"

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.4"
version = "0.1.3"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -37,7 +37,7 @@ serde_json.workspace = true
url = "2.3.1"
ureq = "2.8.0"
criterion = { version = "0.5.1", default-features = false }
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
codspeed-criterion-compat = { version="2.3.0", default-features = false, optional = true}
[dev-dependencies]
ruff_linter.path = "../ruff_linter"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.4"
version = "0.1.3"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -278,7 +278,7 @@ pub struct CheckCommand {
#[arg(long, help_heading = "Rule configuration", hide = true)]
pub dummy_variable_rgx: Option<Regex>,
/// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
#[arg(short, long, help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
@@ -374,7 +374,7 @@ pub struct FormatCommand {
pub config: Option<PathBuf>,
/// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
#[arg(short, long, help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
@@ -409,9 +409,6 @@ pub struct FormatCommand {
force_exclude: bool,
#[clap(long, overrides_with("force_exclude"), hide = true)]
no_force_exclude: bool,
/// Set the line-length.
#[arg(long, help_heading = "Format configuration")]
pub line_length: Option<LineLength>,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
pub isolated: bool,
@@ -555,7 +552,6 @@ impl FormatCommand {
stdin_filename: self.stdin_filename,
},
CliOverrides {
line_length: self.line_length,
respect_gitignore: resolve_bool_arg(
self.respect_gitignore,
self.no_respect_gitignore,

View File

@@ -3,7 +3,6 @@
//! Used for <https://docs.astral.sh/ruff/settings/>.
use std::fmt::Write;
use ruff_python_trivia::textwrap;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
@@ -126,57 +125,22 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab(
"pyproject.toml",
&format_header(parent_set, ConfigurationFile::PyprojectToml),
field.example,
));
output.push_str(&format_tab(
"ruff.toml",
&format_header(parent_set, ConfigurationFile::RuffToml),
field.example,
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
},
field.example
));
output.push('\n');
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
format!(
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
let fmt = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
};
match configuration {
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
ConfigurationFile::RuffToml => {
if fmt.is_empty() {
String::new()
} else {
format!("[{}]", fmt.strip_prefix('.').unwrap())
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
RuffToml,
}
#[derive(Default)]
struct CollectOptionsVisitor {
groups: Vec<(String, OptionSet)>,

View File

@@ -107,30 +107,6 @@ impl Fix {
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
Self {
edits: vec![edit],
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
pub fn applicable_edits(
edit: Edit,
rest: impl IntoIterator<Item = Edit>,
applicability: Applicability,
) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
pub fn min_start(&self) -> Option<TextSize> {
self.edits.first().map(Edit::start)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.4"
version = "0.1.3"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,5 +1,3 @@
obj = {}
key in obj.keys() # SIM118
key not in obj.keys() # SIM118

View File

@@ -1,18 +0,0 @@
import trio
async def foo():
with trio.fail_after():
...
async def foo():
with trio.fail_at():
await ...
async def foo():
with trio.move_on_after():
...
async def foo():
with trio.move_at():
await ...

View File

@@ -1,64 +0,0 @@
import trio
async def func() -> None:
trio.run(foo) # OK, not async
# OK
await trio.aclose_forcefully(foo)
await trio.open_file(foo)
await trio.open_ssl_over_tcp_listeners(foo, foo)
await trio.open_ssl_over_tcp_stream(foo, foo)
await trio.open_tcp_listeners(foo)
await trio.open_tcp_stream(foo, foo)
await trio.open_unix_socket(foo)
await trio.run_process(foo)
await trio.sleep(5)
await trio.sleep_until(5)
await trio.lowlevel.cancel_shielded_checkpoint()
await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint_if_cancelled()
await trio.lowlevel.open_process(foo)
await trio.lowlevel.permanently_detach_coroutine_object(foo)
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
await trio.lowlevel.wait_readable(foo)
await trio.lowlevel.wait_task_rescheduled(foo)
await trio.lowlevel.wait_writable(foo)
# TRIO105
trio.aclose_forcefully(foo)
trio.open_file(foo)
trio.open_ssl_over_tcp_listeners(foo, foo)
trio.open_ssl_over_tcp_stream(foo, foo)
trio.open_tcp_listeners(foo)
trio.open_tcp_stream(foo, foo)
trio.open_unix_socket(foo)
trio.run_process(foo)
trio.serve_listeners(foo, foo)
trio.serve_ssl_over_tcp(foo, foo, foo)
trio.serve_tcp(foo, foo)
trio.sleep(foo)
trio.sleep_forever()
trio.sleep_until(foo)
trio.lowlevel.cancel_shielded_checkpoint()
trio.lowlevel.checkpoint()
trio.lowlevel.checkpoint_if_cancelled()
trio.lowlevel.open_process()
trio.lowlevel.permanently_detach_coroutine_object(foo)
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
trio.lowlevel.temporarily_detach_coroutine_object(foo)
trio.lowlevel.wait_readable(foo)
trio.lowlevel.wait_task_rescheduled(foo)
trio.lowlevel.wait_writable(foo)
async with await trio.open_file(foo): # Ok
pass
async with trio.open_file(foo): # TRIO105
pass
def func() -> None:
# TRIO105 (without fix)
trio.open_file(foo)

View File

@@ -1,28 +0,0 @@
import trio
from trio import sleep
async def func():
await trio.sleep(0) # TRIO115
await trio.sleep(1) # OK
await trio.sleep(0, 1) # OK
await trio.sleep(...) # OK
await trio.sleep() # OK
trio.sleep(0) # TRIO115
foo = 0
trio.sleep(foo) # TRIO115
trio.sleep(1) # OK
time.sleep(0) # OK
sleep(0) # TRIO115
bar = "bar"
trio.sleep(bar)
trio.sleep(0) # TRIO115
def func():
trio.run(trio.sleep(0)) # TRIO115

View File

@@ -1,106 +0,0 @@
def func():
import numpy as np
np.add_docstring
np.add_newdoc
np.add_newdoc_ufunc
np.asfarray([1,2,3])
np.byte_bounds(np.array([1,2,3]))
np.cast
np.cfloat(12+34j)
np.clongfloat(12+34j)
np.compat
np.complex_(12+34j)
np.DataSource
np.deprecate
np.deprecate_with_doc
np.disp(10)
np.fastCopyAndTranspose
np.find_common_type
np.get_array_wrap
np.float_
np.geterrobj
np.Inf
np.Infinity
np.infty
np.issctype
np.issubclass_(np.int32, np.integer)
np.issubsctype
np.mat
np.maximum_sctype
np.NaN
np.nbytes[np.int64]
np.NINF
np.NZERO
np.longcomplex(12+34j)
np.longfloat(12+34j)
np.lookfor
np.obj2sctype(int)
np.PINF
np.PZERO
np.recfromcsv
np.recfromtxt
np.round_(12.34)
np.safe_eval
np.sctype2char
np.sctypes
np.seterrobj
np.set_numeric_ops
np.set_string_function
np.singlecomplex(12+1j)
np.string_("asdf")
np.source
np.tracemalloc_domain
np.unicode_("asf")
np.who()

View File

@@ -69,5 +69,3 @@ while 1:
#: E701:2:3
a = \
5;
#:
with x(y) as z: ...

View File

@@ -4,9 +4,6 @@ if type(res) == type(42):
#: E721
if type(res) != type(""):
pass
#: E721
if type(res) == memoryview:
pass
#: Okay
import types
@@ -50,14 +47,6 @@ if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if isinstance(res, memoryview):
pass
#: Okay
if type(res) is type:
pass
#: E721
if type(res) == type:
pass
#: Okay
def func_histype(a, b, c):
pass

View File

@@ -8,19 +8,3 @@ def ends_in_quote():
def contains_quote():
'Sum"\\mary.'
# OK
def contains_triples(t):
"""('''|\""")"""
# OK
def contains_triples(t):
'''(\'''|""")'''
# TODO: here should raise D300 for using dobule quotes instead,
# because escaped double quote does allow us.
def contains_triples(t):
'''(\""")'''

View File

@@ -31,7 +31,3 @@ def make_unique_pod_id(pod_id: str) -> str | None:
:param pod_id: requested pod name
:return: ``str`` valid Pod name of appropriate length
"""
def shouldnt_add_raw_here2():
u"Sum\\mary."

View File

@@ -1,5 +1,5 @@
def f(tup):
x, y = tup
x, y = tup # this does NOT trigger F841
def f():
@@ -7,17 +7,17 @@ def f():
def f():
(x, y) = coords = 1, 2
(x, y) = coords = 1, 2 # this does NOT trigger F841
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2
(x, y) = coords = 1, 2 # this triggers F841 on coords
def f():
coords = (x, y) = 1, 2
coords = (x, y) = 1, 2 # this triggers F841 on coords
def f():

View File

@@ -1,32 +0,0 @@
"""Test fix for issue #8441.
Ref: https://github.com/astral-sh/ruff/issues/8441
"""
def foo():
...
def bar():
a = foo()
b, c = foo()
def baz():
d, _e = foo()
print(d)
def qux():
f, _ = foo()
print(f)
def quux():
g, h = foo()
print(g, h)
def quuz():
_i, _j = foo()

View File

@@ -57,9 +57,3 @@ r'\%03o' % (ord(c),)
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
"%d" % (1 if x > 0 else 2)
# Special cases for %c allowing single character strings
# https://github.com/astral-sh/ruff/issues/8406
"%c" % ("x",)
"%c" % "x"
"%c" % "œ"

View File

@@ -23,11 +23,3 @@ MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)
# Regression test for: https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788787357
S3File = NamedTuple(
"S3File",
[
("dataHPK",* str),
],
)

View File

@@ -1,68 +0,0 @@
import asyncio, socket
# These should be fixed
try:
pass
except asyncio.TimeoutError:
pass
try:
pass
except socket.timeout:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (asyncio.TimeoutError,):
pass
try:
pass
except (socket.timeout,):
pass
try:
pass
except (asyncio.TimeoutError, socket.timeout,):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (asyncio.TimeoutError, error):
pass
# These should not change
from foo import error
try:
pass
except (TimeoutError, error):
pass
try:
pass
except:
pass
try:
pass
except TimeoutError:
pass
try:
pass
except (TimeoutError, KeyError):
pass

View File

@@ -43,12 +43,6 @@ def yes_four(x: Dict[int, str]):
del x[:]
def yes_five(x: Dict[int, str]):
# FURB131
del x[:]
x = 1
# these should not
del names["key"]

View File

@@ -1,67 +0,0 @@
foo = None
# Error.
type(foo) is type(None)
type(None) is type(foo)
type(None) is type(None)
type(foo) is not type(None)
type(None) is not type(foo)
type(None) is not type(None)
type(foo) == type(None)
type(None) == type(foo)
type(None) == type(None)
type(foo) != type(None)
type(None) != type(foo)
type(None) != type(None)
# Ok.
foo is None
foo is not None
None is foo
None is not foo
None is None
None is not None
foo is type(None)
type(foo) is None
type(None) is None
foo is not type(None)
type(foo) is not None
type(None) is not None
foo == type(None)
type(foo) == None
type(None) == None
foo != type(None)
type(foo) != None
type(None) != None
type(foo) > type(None)

View File

@@ -43,6 +43,3 @@ if 1 is {1}:
if "a" == "a":
pass
if 1 in {*[1]}:
pass

View File

@@ -45,11 +45,3 @@ x = f"string { # And here's a comment with an unusual parenthesis:
# And here's a comment with a greek alpha:
foo # And here's a comment with an unusual punctuation mark:
}"
# At runtime the attribute will be stored as Greek small letter mu instead of
# micro sign because of PEP 3131's NFKC normalization
class Labware:
µL = 1.5
assert getattr(Labware(), "µL") == 1.5

View File

@@ -15,8 +15,8 @@ use crate::rules::{
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
};
use crate::settings::types::PythonVersion;
@@ -158,9 +158,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
@@ -317,9 +314,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_attribute(checker, expr);
}
@@ -466,11 +460,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_call(checker, func);
}
}
if checker.enabled(Rule::NonPEP604Isinstance) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::use_pep604_isinstance(checker, expr, func, args);
@@ -926,12 +915,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ImplicitCwd) {
refurb::rules::no_implicit_cwd(checker, call);
}
if checker.enabled(Rule::TrioSyncCall) {
flake8_trio::rules::sync_call(checker, call);
}
if checker.enabled(Rule::TrioZeroSleepCall) {
flake8_trio::rules::zero_sleep_call(checker, call);
}
}
Expr::Dict(
dict @ ast::ExprDict {
@@ -1241,9 +1224,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
comparators,
);
}
if checker.enabled(Rule::TypeNoneComparison) {
refurb::rules::type_none_comparison(checker, compare);
}
if checker.enabled(Rule::SingleItemMembershipTest) {
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
}

View File

@@ -12,8 +12,8 @@ use crate::rules::{
airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger,
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
flake8_tidy_imports, flake8_trio, flake8_type_checking, mccabe, pandas_vet, pep8_naming,
perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
@@ -1006,13 +1006,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyupgrade::rules::os_error_alias_raise(checker, item);
}
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
if let Some(item) = exc {
pyupgrade::rules::timeout_error_alias_raise(checker, item);
}
}
}
if checker.enabled(Rule::RaiseVanillaClass) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_class(checker, expr);
@@ -1202,9 +1195,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UselessWithLock) {
pylint::rules::useless_with_lock(checker, with_stmt);
}
if checker.enabled(Rule::TrioTimeoutWithoutAwait) {
flake8_trio::rules::timeout_without_await(checker, with_stmt, items);
}
}
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
if checker.enabled(Rule::FunctionUsesLoopVariable) {
@@ -1311,11 +1301,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_handlers(checker, handlers);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_handlers(checker, handlers);
}
}
if checker.enabled(Rule::PytestAssertInExcept) {
flake8_pytest_style::rules::assert_in_exception_handler(checker, handlers);
}

View File

@@ -290,11 +290,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "101") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction),
(Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
(Flake8Builtins, "002") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinArgumentShadowing),
@@ -502,7 +497,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Preview, rules::pyupgrade::rules::TimeoutErrorAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -862,7 +856,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Numpy, "001") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedTypeAlias),
(Numpy, "002") => (RuleGroup::Stable, rules::numpy::rules::NumpyLegacyRandom),
(Numpy, "003") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedFunction),
(Numpy, "201") => (RuleGroup::Preview, rules::numpy::rules::Numpy2Deprecation),
// ruff
(Ruff, "001") => (RuleGroup::Stable, rules::ruff::rules::AmbiguousUnicodeCharacterString),
@@ -946,7 +939,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),

View File

@@ -64,9 +64,6 @@ pub enum Linter {
/// [flake8-async](https://pypi.org/project/flake8-async/)
#[prefix = "ASYNC"]
Flake8Async,
/// [flake8-trio](https://pypi.org/project/flake8-trio/)
#[prefix = "TRIO"]
Flake8Trio,
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
#[prefix = "S"]
Flake8Bandit,

View File

@@ -163,15 +163,11 @@ mod tests {
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
));
assert!(comment_contains_code("# )", &[]));
// This used to return true, but our parser has gotten a bit better
// at rejecting invalid Python syntax. And indeed, this is not valid
// Python code.
assert!(!comment_contains_code(
assert!(comment_contains_code(
"# app_label=\"core\", model=\"user\"",
&[]
));
assert!(comment_contains_code("# )", &[]));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():", &[]));

View File

@@ -27,11 +27,6 @@ use crate::checkers::ast::Checker;
/// raise AssertionError
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as changing an `assert` to a
/// `raise` will change the behavior of your program when running in
/// optimized mode (`python -O`).
///
/// ## References
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
#[violation]

View File

@@ -50,16 +50,6 @@ use crate::checkers::ast::Checker;
/// return arg
/// ```
///
/// If the use of a singleton is intentional, assign the result call to a
/// module-level variable, and use that variable in the default argument:
/// ```python
/// ERROR = ValueError("Hosts weren't successfully added")
///
///
/// def add_host(error: Exception = ERROR) -> None:
/// ...
/// ```
///
/// ## Options
/// - `flake8-bugbear.extend-immutable-calls`
#[violation]
@@ -72,9 +62,9 @@ impl Violation for FunctionCallInDefaultArgument {
fn message(&self) -> String {
let FunctionCallInDefaultArgument { name } = self;
if let Some(name) = name {
format!("Do not perform function call `{name}` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
format!("Do not perform function call `{name}` in argument defaults")
} else {
format!("Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
format!("Do not perform function call in argument defaults")
}
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults
|
101 | # N.B. we're also flagging the function call in the comprehension
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
@@ -9,21 +9,21 @@ B006_B008.py:102:61: B008 Do not perform function call `range` in argument defau
103 | pass
|
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults
|
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
| ^^^^^^^^ B008
107 | pass
|
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults
|
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
| ^^^^^^^^ B008
111 | pass
|
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults
|
124 | # B008
125 | # Flag function calls as default args (including if they are part of a sub-expression)
@@ -32,21 +32,21 @@ B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument d
127 | ...
|
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
130 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
| ^^^^^^^^^^^^^^^^^ B008
131 | pass
|
B006_B008.py:134:30: B008 Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:134:30: B008 Do not perform function call in argument defaults
|
134 | def can_even_catch_lambdas(a=(lambda x: x)()):
| ^^^^^^^^^^^^^^^ B008
135 | ...
|
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
@@ -55,7 +55,7 @@ B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argu
240 | pass
|
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults
|
243 | # Don't flag nested B006 since we can't guarantee that
244 | # it isn't made mutable by the outer operation.
@@ -64,7 +64,7 @@ B006_B008.py:245:22: B008 Do not perform function call `map` in argument default
246 | pass
|
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults
|
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
@@ -72,7 +72,7 @@ B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argum
251 | pass
|
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults
|
24 | def error_due_to_missing_import(data: List[str] = Depends(None)):
| ^^^^^^^^^^^^^ B008

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
@@ -83,14 +83,13 @@ pub(crate) fn unnecessary_call_around_sorted(
expr.range(),
);
diagnostic.try_set_fix(|| {
Ok(Fix::applicable_edit(
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?,
if outer.id == "reversed" {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
let edit =
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?;
if outer.id == "reversed" {
Ok(Fix::unsafe_edit(edit))
} else {
Ok(Fix::safe_edit(edit))
}
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::BindingKind;
@@ -103,23 +103,17 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
.next()
.is_some_and(char::is_alphanumeric)
{
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(" ".to_string(), arguments.range()),
if exception_type.is_some() {
Applicability::Safe
} else {
Applicability::Unsafe
},
));
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
});
} else {
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_deletion(arguments.range()),
if exception_type.is_some() {
Applicability::Safe
} else {
Applicability::Unsafe
},
));
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_deletion(arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_deletion(arguments.range()))
});
}
checker.diagnostics.push(diagnostic);

View File

@@ -55,7 +55,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::InDictKeys, Path::new("SIM118.py"))]
#[test_case(Rule::IfElseBlockInsteadOfDictGet, Path::new("SIM401.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(

View File

@@ -1,10 +1,9 @@
use ruff_diagnostics::Edit;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_diagnostics::{Applicability, Edit};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
use ruff_python_semantic::analyze::typing;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};
@@ -28,19 +27,8 @@ use crate::checkers::ast::Checker;
/// key in foo
/// ```
///
/// ## Fix safety
/// Given `key in obj.keys()`, `obj` _could_ be a dictionary, or it could be
/// another type that defines a `.keys()` method. In the latter case, removing
/// the `.keys()` attribute could lead to a runtime error.
///
/// As such, this rule's fixes are marked as unsafe. In [preview], though,
/// fixes are marked as safe when Ruff can determine that `obj` is a
/// dictionary.
///
/// ## References
/// - [Python documentation: Mapping Types](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct InDictKeys {
operator: String,
@@ -125,28 +113,6 @@ fn key_in_dict(
.skip_trivia()
.find(|token| token.kind == SimpleTokenKind::Dot)
{
// The fix is only safe if we know the expression is a dictionary, since other types
// can define a `.keys()` method.
let applicability = if checker.settings.preview.is_enabled() {
let is_dict = value.as_name_expr().is_some_and(|name| {
let Some(binding) = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
return false;
};
typing::is_dict(binding, checker.semantic())
});
if is_dict {
Applicability::Safe
} else {
Applicability::Unsafe
}
} else {
Applicability::Unsafe
};
// If the `.keys()` is followed by (e.g.) a keyword, we need to insert a space,
// since we're removing parentheses, which could lead to invalid syntax, as in:
// ```python
@@ -160,15 +126,12 @@ fn key_in_dict(
.next()
.is_some_and(|char| char.is_ascii_alphabetic())
{
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(" ".to_string(), range),
applicability,
));
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
" ".to_string(),
range,
)));
} else {
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_deletion(range),
applicability,
));
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(range)));
}
}
checker.diagnostics.push(diagnostic);

View File

@@ -1,401 +1,396 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:1:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
1 | obj = {}
2 |
3 | key in obj.keys() # SIM118
1 | key in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
4 |
5 | key not in obj.keys() # SIM118
2 |
3 | key not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
1 1 | obj = {}
1 |-key in obj.keys() # SIM118
1 |+key in obj # SIM118
2 2 |
3 |-key in obj.keys() # SIM118
3 |+key in obj # SIM118
3 3 | key not in obj.keys() # SIM118
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:3:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
3 | key in obj.keys() # SIM118
4 |
5 | key not in obj.keys() # SIM118
1 | key in obj.keys() # SIM118
2 |
3 | key not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
4 |
5 | foo["bar"] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
1 1 | key in obj.keys() # SIM118
2 2 |
3 |-key not in obj.keys() # SIM118
3 |+key not in obj # SIM118
4 4 |
5 5 | foo["bar"] in obj.keys() # SIM118
6 6 |
SIM118.py:5:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
3 | key not in obj.keys() # SIM118
4 |
5 | foo["bar"] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
7 | foo["bar"] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
2 2 |
3 3 | key in obj.keys() # SIM118
3 3 | key not in obj.keys() # SIM118
4 4 |
5 |-key not in obj.keys() # SIM118
5 |+key not in obj # SIM118
5 |-foo["bar"] in obj.keys() # SIM118
5 |+foo["bar"] in obj # SIM118
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
7 7 | foo["bar"] not in obj.keys() # SIM118
8 8 |
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:7:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
5 | key not in obj.keys() # SIM118
5 | foo["bar"] in obj.keys() # SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
7 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
9 | foo['bar'] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
4 4 |
5 5 | key not in obj.keys() # SIM118
5 5 | foo["bar"] in obj.keys() # SIM118
6 6 |
7 |-foo["bar"] in obj.keys() # SIM118
7 |+foo["bar"] in obj # SIM118
7 |-foo["bar"] not in obj.keys() # SIM118
7 |+foo["bar"] not in obj # SIM118
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
9 9 | foo['bar'] in obj.keys() # SIM118
10 10 |
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:9:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
7 | foo["bar"] in obj.keys() # SIM118
7 | foo["bar"] not in obj.keys() # SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
9 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
11 | foo['bar'] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
7 7 | foo["bar"] not in obj.keys() # SIM118
8 8 |
9 |-foo["bar"] not in obj.keys() # SIM118
9 |+foo["bar"] not in obj # SIM118
9 |-foo['bar'] in obj.keys() # SIM118
9 |+foo['bar'] in obj # SIM118
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
11 11 | foo['bar'] not in obj.keys() # SIM118
12 12 |
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:11:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
9 | foo["bar"] not in obj.keys() # SIM118
9 | foo['bar'] in obj.keys() # SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
11 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
13 | foo() in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
9 9 | foo['bar'] in obj.keys() # SIM118
10 10 |
11 |-foo['bar'] in obj.keys() # SIM118
11 |+foo['bar'] in obj # SIM118
11 |-foo['bar'] not in obj.keys() # SIM118
11 |+foo['bar'] not in obj # SIM118
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
13 13 | foo() in obj.keys() # SIM118
14 14 |
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:13:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
11 | foo['bar'] in obj.keys() # SIM118
11 | foo['bar'] not in obj.keys() # SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
13 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
14 |
15 | foo() in obj.keys() # SIM118
15 | foo() not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
11 11 | foo['bar'] not in obj.keys() # SIM118
12 12 |
13 |-foo['bar'] not in obj.keys() # SIM118
13 |+foo['bar'] not in obj # SIM118
13 |-foo() in obj.keys() # SIM118
13 |+foo() in obj # SIM118
14 14 |
15 15 | foo() in obj.keys() # SIM118
15 15 | foo() not in obj.keys() # SIM118
16 16 |
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:15:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
13 | foo['bar'] not in obj.keys() # SIM118
13 | foo() in obj.keys() # SIM118
14 |
15 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
15 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
16 |
17 | foo() not in obj.keys() # SIM118
17 | for key in obj.keys(): # SIM118
|
= help: Remove `.keys()`
Suggested fix
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
13 13 | foo() in obj.keys() # SIM118
14 14 |
15 |-foo() in obj.keys() # SIM118
15 |+foo() in obj # SIM118
15 |-foo() not in obj.keys() # SIM118
15 |+foo() not in obj # SIM118
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
17 17 | for key in obj.keys(): # SIM118
18 18 | pass
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:17:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
15 | foo() in obj.keys() # SIM118
15 | foo() not in obj.keys() # SIM118
16 |
17 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
18 |
19 | for key in obj.keys(): # SIM118
17 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
18 | pass
|
= help: Remove `.keys()`
Suggested fix
14 14 |
15 15 | foo() in obj.keys() # SIM118
15 15 | foo() not in obj.keys() # SIM118
16 16 |
17 |-foo() not in obj.keys() # SIM118
17 |+foo() not in obj # SIM118
18 18 |
19 19 | for key in obj.keys(): # SIM118
20 20 | pass
17 |-for key in obj.keys(): # SIM118
17 |+for key in obj: # SIM118
18 18 | pass
19 19 |
20 20 | for key in list(obj.keys()):
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:24:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
17 | foo() not in obj.keys() # SIM118
18 |
19 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
20 | pass
22 | del obj[key]
23 |
24 | [k for k in obj.keys()] # SIM118
| ^^^^^^^^^^^^^^^ SIM118
25 |
26 | {k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Suggested fix
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
19 |-for key in obj.keys(): # SIM118
19 |+for key in obj: # SIM118
20 20 | pass
21 21 |
22 22 | for key in list(obj.keys()):
21 21 | if some_property(key):
22 22 | del obj[key]
23 23 |
24 |-[k for k in obj.keys()] # SIM118
24 |+[k for k in obj] # SIM118
25 25 |
26 26 | {k for k in obj.keys()} # SIM118
27 27 |
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
24 | del obj[key]
24 | [k for k in obj.keys()] # SIM118
25 |
26 | [k for k in obj.keys()] # SIM118
26 | {k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
28 | {k: k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Suggested fix
23 23 | if some_property(key):
24 24 | del obj[key]
23 23 |
24 24 | [k for k in obj.keys()] # SIM118
25 25 |
26 |-[k for k in obj.keys()] # SIM118
26 |+[k for k in obj] # SIM118
26 |-{k for k in obj.keys()} # SIM118
26 |+{k for k in obj} # SIM118
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
28 28 | {k: k for k in obj.keys()} # SIM118
29 29 |
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:28:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
26 | [k for k in obj.keys()] # SIM118
26 | {k for k in obj.keys()} # SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Suggested fix
25 25 |
26 26 | [k for k in obj.keys()] # SIM118
27 27 |
28 |-{k for k in obj.keys()} # SIM118
28 |+{k for k in obj} # SIM118
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k for k in obj.keys()} # SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
28 | {k: k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | (k for k in obj.keys()) # SIM118
|
= help: Remove `.keys()`
Suggested fix
25 25 |
26 26 | {k for k in obj.keys()} # SIM118
27 27 |
28 |-{k: k for k in obj.keys()} # SIM118
28 |+{k: k for k in obj} # SIM118
29 29 |
30 30 | (k for k in obj.keys()) # SIM118
31 31 |
SIM118.py:30:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k: k for k in obj.keys()} # SIM118
29 |
30 | (k for k in obj.keys()) # SIM118
| ^^^^^^^^^^^^^^^ SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
32 | key in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
28 28 | {k: k for k in obj.keys()} # SIM118
29 29 |
30 |-{k: k for k in obj.keys()} # SIM118
30 |+{k: k for k in obj} # SIM118
30 |-(k for k in obj.keys()) # SIM118
30 |+(k for k in obj) # SIM118
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
32 32 | key in (obj or {}).keys() # SIM118
33 33 |
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:32:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
30 | {k: k for k in obj.keys()} # SIM118
30 | (k for k in obj.keys()) # SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
| ^^^^^^^^^^^^^^^ SIM118
32 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
34 | (key) in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
30 30 | (k for k in obj.keys()) # SIM118
31 31 |
32 |-(k for k in obj.keys()) # SIM118
32 |+(k for k in obj) # SIM118
32 |-key in (obj or {}).keys() # SIM118
32 |+key in (obj or {}) # SIM118
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
34 34 | (key) in (obj or {}).keys() # SIM118
35 35 |
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
32 | (k for k in obj.keys()) # SIM118
32 | key in (obj or {}).keys() # SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
34 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
36 | from typing import KeysView
|
= help: Remove `.keys()`
Suggested fix
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
32 32 | key in (obj or {}).keys() # SIM118
33 33 |
34 |-key in (obj or {}).keys() # SIM118
34 |+key in (obj or {}) # SIM118
34 |-(key) in (obj or {}).keys() # SIM118
34 |+(key) in (obj or {}) # SIM118
35 35 |
36 36 | (key) in (obj or {}).keys() # SIM118
36 36 | from typing import KeysView
37 37 |
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:48:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
34 | key in (obj or {}).keys() # SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
37 |
38 | from typing import KeysView
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
49 | (key in obj.keys())and foo
50 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
36 |-(key) in (obj or {}).keys() # SIM118
36 |+(key) in (obj or {}) # SIM118
37 37 |
38 38 | from typing import KeysView
39 39 |
45 45 |
46 46 |
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 |-key in obj.keys()and foo
48 |+key in obj and foo
49 49 | (key in obj.keys())and foo
50 50 | key in (obj.keys())and foo
51 51 |
SIM118.py:49:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 | key in obj.keys()and foo
49 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
50 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
46 46 |
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 48 | key in obj.keys()and foo
49 |-(key in obj.keys())and foo
49 |+(key in obj)and foo
50 50 | key in (obj.keys())and foo
51 51 |
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
47 47 |
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 |-key in obj.keys()and foo
50 |+key in obj and foo
51 51 | (key in obj.keys())and foo
52 52 | key in (obj.keys())and foo
53 53 |
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 |-(key in obj.keys())and foo
51 |+(key in obj)and foo
52 52 | key in (obj.keys())and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
48 | key in obj.keys()and foo
49 | (key in obj.keys())and foo
50 | key in (obj.keys())and foo
| ^^^^^^^^^^^^^^^^^^^ SIM118
53 |
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
51 |
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
= help: Remove `.keys()`
Suggested fix
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 51 | (key in obj.keys())and foo
52 |-key in (obj.keys())and foo
52 |+key in (obj)and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 55 | for key in (
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 48 | key in obj.keys()and foo
49 49 | (key in obj.keys())and foo
50 |-key in (obj.keys())and foo
50 |+key in (obj)and foo
51 51 |
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
53 53 | for key in (
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:53:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 | for key in (
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
53 | for key in (
| _____^
56 | | self.experiment.surveys[0]
57 | | .stations[0]
58 | | .keys()
59 | | ):
54 | | self.experiment.surveys[0]
55 | | .stations[0]
56 | | .keys()
57 | | ):
| |_^ SIM118
60 | continue
58 | continue
|
= help: Remove `.keys()`
Suggested fix
55 55 | for key in (
56 56 | self.experiment.surveys[0]
57 57 | .stations[0]
58 |- .keys()
58 |+
59 59 | ):
60 60 | continue
53 53 | for key in (
54 54 | self.experiment.surveys[0]
55 55 | .stations[0]
56 |- .keys()
56 |+
57 57 | ):
58 58 | continue

View File

@@ -1,401 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
1 | obj = {}
2 |
3 | key in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
4 |
5 | key not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
1 1 | obj = {}
2 2 |
3 |-key in obj.keys() # SIM118
3 |+key in obj # SIM118
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
3 | key in obj.keys() # SIM118
4 |
5 | key not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
2 2 |
3 3 | key in obj.keys() # SIM118
4 4 |
5 |-key not in obj.keys() # SIM118
5 |+key not in obj # SIM118
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
5 | key not in obj.keys() # SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
7 |-foo["bar"] in obj.keys() # SIM118
7 |+foo["bar"] in obj # SIM118
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
7 | foo["bar"] in obj.keys() # SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
9 |-foo["bar"] not in obj.keys() # SIM118
9 |+foo["bar"] not in obj # SIM118
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
9 | foo["bar"] not in obj.keys() # SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
11 |-foo['bar'] in obj.keys() # SIM118
11 |+foo['bar'] in obj # SIM118
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
11 | foo['bar'] in obj.keys() # SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
14 |
15 | foo() in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
13 |-foo['bar'] not in obj.keys() # SIM118
13 |+foo['bar'] not in obj # SIM118
14 14 |
15 15 | foo() in obj.keys() # SIM118
16 16 |
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
13 | foo['bar'] not in obj.keys() # SIM118
14 |
15 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
16 |
17 | foo() not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
15 |-foo() in obj.keys() # SIM118
15 |+foo() in obj # SIM118
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
15 | foo() in obj.keys() # SIM118
16 |
17 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
18 |
19 | for key in obj.keys(): # SIM118
|
= help: Remove `.keys()`
Fix
14 14 |
15 15 | foo() in obj.keys() # SIM118
16 16 |
17 |-foo() not in obj.keys() # SIM118
17 |+foo() not in obj # SIM118
18 18 |
19 19 | for key in obj.keys(): # SIM118
20 20 | pass
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
17 | foo() not in obj.keys() # SIM118
18 |
19 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
20 | pass
|
= help: Remove `.keys()`
Fix
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
19 |-for key in obj.keys(): # SIM118
19 |+for key in obj: # SIM118
20 20 | pass
21 21 |
22 22 | for key in list(obj.keys()):
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
24 | del obj[key]
25 |
26 | [k for k in obj.keys()] # SIM118
| ^^^^^^^^^^^^^^^ SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Fix
23 23 | if some_property(key):
24 24 | del obj[key]
25 25 |
26 |-[k for k in obj.keys()] # SIM118
26 |+[k for k in obj] # SIM118
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
26 | [k for k in obj.keys()] # SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Fix
25 25 |
26 26 | [k for k in obj.keys()] # SIM118
27 27 |
28 |-{k for k in obj.keys()} # SIM118
28 |+{k for k in obj} # SIM118
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k for k in obj.keys()} # SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
|
= help: Remove `.keys()`
Fix
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
30 |-{k: k for k in obj.keys()} # SIM118
30 |+{k: k for k in obj} # SIM118
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
30 | {k: k for k in obj.keys()} # SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
| ^^^^^^^^^^^^^^^ SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Fix
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
32 |-(k for k in obj.keys()) # SIM118
32 |+(k for k in obj) # SIM118
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
32 | (k for k in obj.keys()) # SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
34 |-key in (obj or {}).keys() # SIM118
34 |+key in (obj or {}) # SIM118
35 35 |
36 36 | (key) in (obj or {}).keys() # SIM118
37 37 |
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
34 | key in (obj or {}).keys() # SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
37 |
38 | from typing import KeysView
|
= help: Remove `.keys()`
Suggested fix
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
36 |-(key) in (obj or {}).keys() # SIM118
36 |+(key) in (obj or {}) # SIM118
37 37 |
38 38 | from typing import KeysView
39 39 |
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Fix
47 47 |
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 |-key in obj.keys()and foo
50 |+key in obj and foo
51 51 | (key in obj.keys())and foo
52 52 | key in (obj.keys())and foo
53 53 |
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Fix
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 |-(key in obj.keys())and foo
51 |+(key in obj)and foo
52 52 | key in (obj.keys())and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
| ^^^^^^^^^^^^^^^^^^^ SIM118
53 |
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
= help: Remove `.keys()`
Fix
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 51 | (key in obj.keys())and foo
52 |-key in (obj.keys())and foo
52 |+key in (obj)and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 55 | for key in (
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 | for key in (
| _____^
56 | | self.experiment.surveys[0]
57 | | .stations[0]
58 | | .keys()
59 | | ):
| |_^ SIM118
60 | continue
|
= help: Remove `.keys()`
Suggested fix
55 55 | for key in (
56 56 | self.experiment.surveys[0]
57 57 | .stations[0]
58 |- .keys()
58 |+
59 59 | ):
60 60 | continue

View File

@@ -1,157 +0,0 @@
use ruff_python_ast::call_path::CallPath;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) enum MethodName {
AcloseForcefully,
CancelScope,
CancelShieldedCheckpoint,
Checkpoint,
CheckpointIfCancelled,
FailAfter,
FailAt,
MoveOnAfter,
MoveOnAt,
OpenFile,
OpenProcess,
OpenSslOverTcpListeners,
OpenSslOverTcpStream,
OpenTcpListeners,
OpenTcpStream,
OpenUnixSocket,
PermanentlyDetachCoroutineObject,
ReattachDetachedCoroutineObject,
RunProcess,
ServeListeners,
ServeSslOverTcp,
ServeTcp,
Sleep,
SleepForever,
TemporarilyDetachCoroutineObject,
WaitReadable,
WaitTaskRescheduled,
WaitWritable,
}
impl MethodName {
/// Returns `true` if the method is async, `false` if it is sync.
pub(super) fn is_async(self) -> bool {
match self {
MethodName::AcloseForcefully
| MethodName::CancelShieldedCheckpoint
| MethodName::Checkpoint
| MethodName::CheckpointIfCancelled
| MethodName::OpenFile
| MethodName::OpenProcess
| MethodName::OpenSslOverTcpListeners
| MethodName::OpenSslOverTcpStream
| MethodName::OpenTcpListeners
| MethodName::OpenTcpStream
| MethodName::OpenUnixSocket
| MethodName::PermanentlyDetachCoroutineObject
| MethodName::ReattachDetachedCoroutineObject
| MethodName::RunProcess
| MethodName::ServeListeners
| MethodName::ServeSslOverTcp
| MethodName::ServeTcp
| MethodName::Sleep
| MethodName::SleepForever
| MethodName::TemporarilyDetachCoroutineObject
| MethodName::WaitReadable
| MethodName::WaitTaskRescheduled
| MethodName::WaitWritable => true,
MethodName::MoveOnAfter
| MethodName::MoveOnAt
| MethodName::FailAfter
| MethodName::FailAt
| MethodName::CancelScope => false,
}
}
}
impl MethodName {
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
match call_path.as_slice() {
["trio", "CancelScope"] => Some(Self::CancelScope),
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
["trio", "fail_after"] => Some(Self::FailAfter),
["trio", "fail_at"] => Some(Self::FailAt),
["trio", "lowlevel", "cancel_shielded_checkpoint"] => {
Some(Self::CancelShieldedCheckpoint)
}
["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint),
["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled),
["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess),
["trio", "lowlevel", "permanently_detach_coroutine_object"] => {
Some(Self::PermanentlyDetachCoroutineObject)
}
["trio", "lowlevel", "reattach_detached_coroutine_object"] => {
Some(Self::ReattachDetachedCoroutineObject)
}
["trio", "lowlevel", "temporarily_detach_coroutine_object"] => {
Some(Self::TemporarilyDetachCoroutineObject)
}
["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable),
["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled),
["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable),
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
["trio", "move_on_at"] => Some(Self::MoveOnAt),
["trio", "open_file"] => Some(Self::OpenFile),
["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners),
["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream),
["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners),
["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream),
["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket),
["trio", "run_process"] => Some(Self::RunProcess),
["trio", "serve_listeners"] => Some(Self::ServeListeners),
["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp),
["trio", "serve_tcp"] => Some(Self::ServeTcp),
["trio", "sleep"] => Some(Self::Sleep),
["trio", "sleep_forever"] => Some(Self::SleepForever),
_ => None,
}
}
}
impl std::fmt::Display for MethodName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"),
MethodName::CancelScope => write!(f, "trio.CancelScope"),
MethodName::CancelShieldedCheckpoint => {
write!(f, "trio.lowlevel.cancel_shielded_checkpoint")
}
MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"),
MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"),
MethodName::FailAfter => write!(f, "trio.fail_after"),
MethodName::FailAt => write!(f, "trio.fail_at"),
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
MethodName::OpenFile => write!(f, "trio.open_file"),
MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"),
MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"),
MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"),
MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"),
MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"),
MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"),
MethodName::PermanentlyDetachCoroutineObject => {
write!(f, "trio.lowlevel.permanently_detach_coroutine_object")
}
MethodName::ReattachDetachedCoroutineObject => {
write!(f, "trio.lowlevel.reattach_detached_coroutine_object")
}
MethodName::RunProcess => write!(f, "trio.run_process"),
MethodName::ServeListeners => write!(f, "trio.serve_listeners"),
MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"),
MethodName::ServeTcp => write!(f, "trio.serve_tcp"),
MethodName::Sleep => write!(f, "trio.sleep"),
MethodName::SleepForever => write!(f, "trio.sleep_forever"),
MethodName::TemporarilyDetachCoroutineObject => {
write!(f, "trio.lowlevel.temporarily_detach_coroutine_object")
}
MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"),
MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"),
MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"),
}
}
}

View File

@@ -1,29 +0,0 @@
//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/).
pub(super) mod method_name;
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::LinterSettings;
use crate::test::test_path;
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
#[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))]
#[test_case(Rule::TrioZeroSleepCall, Path::new("TRIO115.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_trio").join(path).as_path(),
&LinterSettings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,7 +0,0 @@
pub(crate) use sync_call::*;
pub(crate) use timeout_without_await::*;
pub(crate) use zero_sleep_call::*;
mod sync_call;
mod timeout_without_await;
mod zero_sleep_call;

View File

@@ -1,87 +0,0 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, ExprCall};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::rules::flake8_trio::method_name::MethodName;
/// ## What it does
/// Checks for calls to trio functions that are not immediately awaited.
///
/// ## Why is this bad?
/// Many of the functions exposed by trio are asynchronous, and must be awaited
/// to take effect. Calling a trio function without an `await` can lead to
/// `RuntimeWarning` diagnostics and unexpected behaviour.
///
/// ## Example
/// ```python
/// async def double_sleep(x):
/// trio.sleep(2 * x)
/// ```
///
/// Use instead:
/// ```python
/// async def double_sleep(x):
/// await trio.sleep(2 * x)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as adding an `await` to a function
/// call changes its semantics and runtime behavior.
#[violation]
pub struct TrioSyncCall {
method_name: MethodName,
}
impl Violation for TrioSyncCall {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let Self { method_name } = self;
format!("Call to `{method_name}` is not immediately awaited")
}
fn fix_title(&self) -> Option<String> {
Some(format!("Add `await`"))
}
}
/// TRIO105
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
let Some(method_name) = ({
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
return;
};
MethodName::try_from(&call_path)
}) else {
return;
};
if !method_name.is_async() {
return;
}
if checker
.semantic()
.current_expression_parent()
.is_some_and(Expr::is_await_expr)
{
return;
};
let mut diagnostic = Diagnostic::new(TrioSyncCall { method_name }, call.range);
if checker.semantic().in_async_context() {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
pad(
"await".to_string(),
TextRange::new(call.func.start(), call.func.start()),
checker.locator(),
),
call.func.start(),
)));
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,81 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::AwaitVisitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{StmtWith, WithItem};
use crate::checkers::ast::Checker;
use crate::rules::flake8_trio::method_name::MethodName;
/// ## What it does
/// Checks for trio functions that should contain await but don't.
///
/// ## Why is this bad?
/// Some trio context managers, such as `trio.fail_after` and
/// `trio.move_on_after`, have no effect unless they contain an `await`
/// statement. The use of such functions without an `await` statement is
/// likely a mistake.
///
/// ## Example
/// ```python
/// async def func():
/// with trio.move_on_after(2):
/// do_something()
/// ```
///
/// Use instead:
/// ```python
/// async def func():
/// with trio.move_on_after(2):
/// do_something()
/// await awaitable()
/// ```
#[violation]
pub struct TrioTimeoutWithoutAwait {
method_name: MethodName,
}
impl Violation for TrioTimeoutWithoutAwait {
#[derive_message_formats]
fn message(&self) -> String {
let Self { method_name } = self;
format!("A `with {method_name}(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.")
}
}
/// TRIO100
pub(crate) fn timeout_without_await(
checker: &mut Checker,
with_stmt: &StmtWith,
with_items: &[WithItem],
) {
let Some(method_name) = with_items.iter().find_map(|item| {
let call = item.context_expr.as_call_expr()?;
let call_path = checker.semantic().resolve_call_path(call.func.as_ref())?;
MethodName::try_from(&call_path)
}) else {
return;
};
if !matches!(
method_name,
MethodName::MoveOnAfter
| MethodName::MoveOnAt
| MethodName::FailAfter
| MethodName::FailAt
| MethodName::CancelScope
) {
return;
}
let mut visitor = AwaitVisitor::default();
visitor.visit_body(&with_stmt.body);
if visitor.seen_await {
return;
}
checker.diagnostics.push(Diagnostic::new(
TrioTimeoutWithoutAwait { method_name },
with_stmt.range,
));
}

View File

@@ -1,109 +0,0 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Stmt;
use ruff_python_ast::{self as ast, Expr, ExprCall, Int};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
/// ## What it does
/// Checks for uses of `trio.sleep(0)`.
///
/// ## Why is this bad?
/// `trio.sleep(0)` is equivalent to calling `trio.lowlevel.checkpoint()`.
/// However, the latter better conveys the intent of the code.
///
/// ## Example
/// ```python
/// async def func():
/// await trio.sleep(0)
/// ```
///
/// Use instead:
/// ```python
/// async def func():
/// await trio.lowlevel.checkpoint()
/// ```
#[violation]
pub struct TrioZeroSleepCall;
impl AlwaysFixableViolation for TrioZeroSleepCall {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`")
}
fn fix_title(&self) -> String {
format!("Replace with `trio.lowlevel.checkpoint()`")
}
}
/// TRIO115
pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
if !checker
.semantic()
.resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["trio", "sleep"]))
{
return;
}
if call.arguments.len() != 1 {
return;
}
let Some(arg) = call.arguments.find_argument("seconds", 0) else {
return;
};
match arg {
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
let Some(int) = value.as_int() else { return };
if *int != Int::ZERO {
return;
}
}
Expr::Name(ast::ExprName { id, .. }) => {
let scope = checker.semantic().current_scope();
if let Some(binding_id) = scope.get(id) {
let binding = checker.semantic().binding(binding_id);
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
if let Some(parent_id) = binding.source {
let parent = checker.semantic().statement(parent_id);
if let Stmt::Assign(ast::StmtAssign { value, .. })
| Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..
})
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
{
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) =
value.as_ref()
else {
return;
};
let Some(int) = num.as_int() else { return };
if *int != Int::ZERO {
return;
}
}
}
}
}
}
_ => return,
}
let mut diagnostic = Diagnostic::new(TrioZeroSleepCall, call.range());
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("trio", "lowlevel.checkpoint"),
call.func.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, call.func.range());
let arg_edit = Edit::range_deletion(call.arguments.range);
Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit]))
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,26 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO100.py:5:5: TRIO100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
4 | async def foo():
5 | with trio.fail_after():
| _____^
6 | | ...
| |___________^ TRIO100
7 |
8 | async def foo():
|
TRIO100.py:13:5: TRIO100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
12 | async def foo():
13 | with trio.move_on_after():
| _____^
14 | | ...
| |___________^ TRIO100
15 |
16 | async def foo():
|

View File

@@ -1,514 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
29 | # TRIO105
30 | trio.aclose_forcefully(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
= help: Add `await`
Suggested fix
27 27 | await trio.lowlevel.wait_writable(foo)
28 28 |
29 29 | # TRIO105
30 |- trio.aclose_forcefully(foo)
30 |+ await trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
29 | # TRIO105
30 | trio.aclose_forcefully(foo)
31 | trio.open_file(foo)
| ^^^^^^^^^^^^^^^^^^^ TRIO105
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
= help: Add `await`
Suggested fix
28 28 |
29 29 | # TRIO105
30 30 | trio.aclose_forcefully(foo)
31 |- trio.open_file(foo)
31 |+ await trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited
|
30 | trio.aclose_forcefully(foo)
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
|
= help: Add `await`
Suggested fix
29 29 | # TRIO105
30 30 | trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 |- trio.open_ssl_over_tcp_listeners(foo, foo)
32 |+ await trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited
|
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
|
= help: Add `await`
Suggested fix
30 30 | trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 |- trio.open_ssl_over_tcp_stream(foo, foo)
33 |+ await trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
|
= help: Add `await`
Suggested fix
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 |- trio.open_tcp_listeners(foo)
34 |+ await trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately awaited
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
|
= help: Add `await`
Suggested fix
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 |- trio.open_tcp_stream(foo, foo)
35 |+ await trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately awaited
|
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
|
= help: Add `await`
Suggested fix
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 |- trio.open_unix_socket(foo)
36 |+ await trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately awaited
|
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
| ^^^^^^^^^^^^^^^^^^^^^ TRIO105
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
= help: Add `await`
Suggested fix
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 |- trio.run_process(foo)
37 |+ await trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately awaited
|
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
|
= help: Add `await`
Suggested fix
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 |- trio.serve_listeners(foo, foo)
38 |+ await trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited
|
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
|
= help: Add `await`
Suggested fix
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 |- trio.serve_ssl_over_tcp(foo, foo, foo)
39 |+ await trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
41 | trio.sleep(foo)
42 | trio.sleep_forever()
|
= help: Add `await`
Suggested fix
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 |- trio.serve_tcp(foo, foo)
40 |+ await trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited
|
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
| ^^^^^^^^^^^^^^^ TRIO105
42 | trio.sleep_forever()
43 | trio.sleep_until(foo)
|
= help: Add `await`
Suggested fix
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 |- trio.sleep(foo)
41 |+ await trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awaited
|
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
42 | trio.sleep_forever()
| ^^^^^^^^^^^^^^^^^^^^ TRIO105
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
= help: Add `await`
Suggested fix
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 |- trio.sleep_forever()
42 |+ await trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited
|
42 | trio.sleep_forever()
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
|
= help: Add `await`
Suggested fix
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 |- trio.lowlevel.cancel_shielded_checkpoint()
44 |+ await trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited
|
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
45 | trio.lowlevel.checkpoint()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
|
= help: Add `await`
Suggested fix
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 |- trio.lowlevel.checkpoint()
45 |+ await trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
= help: Add `await`
Suggested fix
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 |- trio.lowlevel.checkpoint_if_cancelled()
46 |+ await trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited
|
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
= help: Add `await`
Suggested fix
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 |- trio.lowlevel.open_process()
47 |+ await trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited
|
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
= help: Add `await`
Suggested fix
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 |- trio.lowlevel.permanently_detach_coroutine_object(foo)
48 |+ await trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited
|
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
|
= help: Add `await`
Suggested fix
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 |- trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
49 |+ await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
= help: Add `await`
Suggested fix
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 |- trio.lowlevel.temporarily_detach_coroutine_object(foo)
50 |+ await trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
52 | trio.lowlevel.wait_task_rescheduled(foo)
53 | trio.lowlevel.wait_writable(foo)
|
= help: Add `await`
Suggested fix
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 |- trio.lowlevel.wait_readable(foo)
51 |+ await trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
54 54 |
TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
53 | trio.lowlevel.wait_writable(foo)
|
= help: Add `await`
Suggested fix
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 |- trio.lowlevel.wait_task_rescheduled(foo)
52 |+ await trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
54 54 |
55 55 | async with await trio.open_file(foo): # Ok
TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited
|
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
53 | trio.lowlevel.wait_writable(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
54 |
55 | async with await trio.open_file(foo): # Ok
|
= help: Add `await`
Suggested fix
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 |- trio.lowlevel.wait_writable(foo)
53 |+ await trio.lowlevel.wait_writable(foo)
54 54 |
55 55 | async with await trio.open_file(foo): # Ok
56 56 | pass
TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
56 | pass
57 |
58 | async with trio.open_file(foo): # TRIO105
| ^^^^^^^^^^^^^^^^^^^ TRIO105
59 | pass
|
= help: Add `await`
Suggested fix
55 55 | async with await trio.open_file(foo): # Ok
56 56 | pass
57 57 |
58 |- async with trio.open_file(foo): # TRIO105
58 |+ async with await trio.open_file(foo): # TRIO105
59 59 | pass
60 60 |
61 61 |
TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited
|
62 | def func() -> None:
63 | # TRIO105 (without fix)
64 | trio.open_file(foo)
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
= help: Add `await`

View File

@@ -1,119 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO115.py:6:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
5 | async def func():
6 | await trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
7 | await trio.sleep(1) # OK
8 | await trio.sleep(0, 1) # OK
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
3 3 |
4 4 |
5 5 | async def func():
6 |- await trio.sleep(0) # TRIO115
6 |+ await trio.lowlevel.checkpoint # TRIO115
7 7 | await trio.sleep(1) # OK
8 8 | await trio.sleep(0, 1) # OK
9 9 | await trio.sleep(...) # OK
TRIO115.py:12:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
10 | await trio.sleep() # OK
11 |
12 | trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
13 | foo = 0
14 | trio.sleep(foo) # TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
9 9 | await trio.sleep(...) # OK
10 10 | await trio.sleep() # OK
11 11 |
12 |- trio.sleep(0) # TRIO115
12 |+ trio.lowlevel.checkpoint # TRIO115
13 13 | foo = 0
14 14 | trio.sleep(foo) # TRIO115
15 15 | trio.sleep(1) # OK
TRIO115.py:14:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
12 | trio.sleep(0) # TRIO115
13 | foo = 0
14 | trio.sleep(foo) # TRIO115
| ^^^^^^^^^^^^^^^ TRIO115
15 | trio.sleep(1) # OK
16 | time.sleep(0) # OK
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
11 11 |
12 12 | trio.sleep(0) # TRIO115
13 13 | foo = 0
14 |- trio.sleep(foo) # TRIO115
14 |+ trio.lowlevel.checkpoint # TRIO115
15 15 | trio.sleep(1) # OK
16 16 | time.sleep(0) # OK
17 17 |
TRIO115.py:18:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
16 | time.sleep(0) # OK
17 |
18 | sleep(0) # TRIO115
| ^^^^^^^^ TRIO115
19 |
20 | bar = "bar"
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
15 15 | trio.sleep(1) # OK
16 16 | time.sleep(0) # OK
17 17 |
18 |- sleep(0) # TRIO115
18 |+ trio.lowlevel.checkpoint # TRIO115
19 19 |
20 20 | bar = "bar"
21 21 | trio.sleep(bar)
TRIO115.py:24:1: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
24 | trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
21 21 | trio.sleep(bar)
22 22 |
23 23 |
24 |-trio.sleep(0) # TRIO115
24 |+trio.lowlevel.checkpoint # TRIO115
25 25 |
26 26 |
27 27 | def func():
TRIO115.py:28:14: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
27 | def func():
28 | trio.run(trio.sleep(0)) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
25 25 |
26 26 |
27 27 | def func():
28 |- trio.run(trio.sleep(0)) # TRIO115
28 |+ trio.run(trio.lowlevel.checkpoint) # TRIO115

View File

@@ -37,7 +37,6 @@ pub mod flake8_simplify;
pub mod flake8_slots;
pub mod flake8_tidy_imports;
pub mod flake8_todos;
pub mod flake8_trio;
pub mod flake8_type_checking;
pub mod flake8_unused_arguments;
pub mod flake8_use_pathlib;

View File

@@ -16,7 +16,6 @@ mod tests {
#[test_case(Rule::NumpyDeprecatedTypeAlias, Path::new("NPY001.py"))]
#[test_case(Rule::NumpyLegacyRandom, Path::new("NPY002.py"))]
#[test_case(Rule::NumpyDeprecatedFunction, Path::new("NPY003.py"))]
#[test_case(Rule::Numpy2Deprecation, Path::new("NPY201.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,9 +1,7 @@
pub(crate) use deprecated_function::*;
pub(crate) use deprecated_type_alias::*;
pub(crate) use legacy_random::*;
pub(crate) use numpy_2_0_deprecation::*;
mod deprecated_function;
mod deprecated_type_alias;
mod legacy_random;
mod numpy_2_0_deprecation;

View File

@@ -1,562 +0,0 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
/// ## What it does
/// Checks for uses of NumPy functions and constants that were removed from
/// the main namespace in NumPy 2.0.
///
/// ## Why is this bad?
/// NumPy 2.0 includes an overhaul of NumPy's Python API, intended to remove
/// redundant aliases and routines, and establish unambiguous mechanisms for
/// accessing constants, dtypes, and functions.
///
/// As part of this overhaul, a variety of deprecated NumPy functions and
/// constants were removed from the main namespace.
///
/// The majority of these functions and constants can be automatically replaced
/// by other members of the NumPy API or by equivalents from the Python
/// standard library. With the exception of renaming `numpy.byte_bounds` to
/// `numpy.lib.array_utils.byte_bounds`, all such replacements are backwards
/// compatible with earlier versions of NumPy.
///
/// This rule flags all uses of removed members, along with automatic fixes for
/// any backwards-compatible replacements.
///
/// ## Examples
/// ```python
/// import numpy as np
///
/// arr1 = [np.Infinity, np.NaN, np.nan, np.PINF, np.inf]
/// arr2 = [np.float_(1.5), np.float64(5.1)]
/// np.round_(arr2)
/// ```
///
/// Use instead:
/// ```python
/// import numpy as np
///
/// arr1 = [np.inf, np.nan, np.nan, np.inf, np.inf]
/// arr2 = [np.float64(1.5), np.float64(5.1)]
/// np.round(arr2)
/// ```
#[violation]
pub struct Numpy2Deprecation {
existing: String,
migration_guide: Option<String>,
code_action: Option<String>,
}
impl Violation for Numpy2Deprecation {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let Numpy2Deprecation {
existing,
migration_guide,
code_action: _,
} = self;
match migration_guide {
Some(migration_guide) => {
format!("`np.{existing}` will be removed in NumPy 2.0. {migration_guide}",)
}
None => format!("`np.{existing}` will be removed without replacement in NumPy 2.0"),
}
}
fn fix_title(&self) -> Option<String> {
let Numpy2Deprecation {
existing: _,
migration_guide: _,
code_action,
} = self;
code_action.clone()
}
}
#[derive(Debug)]
struct Replacement<'a> {
existing: &'a str,
details: Details<'a>,
}
#[derive(Debug)]
enum Details<'a> {
/// The deprecated member can be replaced by another member in the NumPy API.
AutoImport {
path: &'a str,
name: &'a str,
compatibility: Compatibility,
},
/// The deprecated member can be replaced by a member of the Python standard library.
AutoPurePython { python_expr: &'a str },
/// The deprecated member can be replaced by a manual migration.
Manual { guideline: Option<&'a str> },
}
impl Details<'_> {
fn guideline(&self) -> Option<String> {
match self {
Details::AutoImport {
path,
name,
compatibility: Compatibility::BackwardsCompatible,
} => Some(format!("Use `{path}.{name}` instead.")),
Details::AutoImport {
path,
name,
compatibility: Compatibility::Breaking,
} => Some(format!(
"Use `{path}.{name}` on NumPy 2.0, or ignore this warning on earlier versions."
)),
Details::AutoPurePython { python_expr } => {
Some(format!("Use `{python_expr}` instead."))
}
Details::Manual { guideline } => guideline.map(ToString::to_string),
}
}
fn code_action(&self) -> Option<String> {
match self {
Details::AutoImport {
path,
name,
compatibility: Compatibility::BackwardsCompatible,
} => Some(format!("Replace with `{path}.{name}`")),
Details::AutoImport {
path,
name,
compatibility: Compatibility::Breaking,
} => Some(format!(
"Replace with `{path}.{name}` (requires NumPy 2.0 or greater)"
)),
Details::AutoPurePython { python_expr } => {
Some(format!("Replace with `{python_expr}`"))
}
Details::Manual { guideline: _ } => None,
}
}
}
#[derive(Debug)]
enum Compatibility {
/// The changes is backwards compatible with earlier versions of NumPy.
BackwardsCompatible,
/// The change is breaking in NumPy 2.0.
Breaking,
}
/// NPY201
pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
let maybe_replacement = checker
.semantic()
.resolve_call_path(expr)
.and_then(|call_path| match call_path.as_slice() {
// NumPy's main namespace np.* members removed in 2.0
["numpy", "add_docstring"] => Some(Replacement {
existing: "add_docstring",
details: Details::AutoImport {
path: "numpy.lib",
name: "add_docstring",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "add_newdoc"] => Some(Replacement {
existing: "add_newdoc",
details: Details::AutoImport {
path: "numpy.lib",
name: "add_newdoc",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "add_newdoc_ufunc"] => Some(Replacement {
existing: "add_newdoc_ufunc",
details: Details::Manual {
guideline: Some("`add_newdoc_ufunc` is an internal function."),
},
}),
["numpy", "asfarray"] => Some(Replacement {
existing: "asfarray",
details: Details::Manual {
guideline: Some("Use `np.asarray` with a `float` dtype instead."),
},
}),
["numpy", "byte_bounds"] => Some(Replacement {
existing: "byte_bounds",
details: Details::AutoImport {
path: "numpy.lib.array_utils",
name: "byte_bounds",
compatibility: Compatibility::Breaking,
},
}),
["numpy", "cast"] => Some(Replacement {
existing: "cast",
details: Details::Manual {
guideline: Some("Use `np.asarray(arr, dtype=dtype)` instead."),
},
}),
["numpy", "cfloat"] => Some(Replacement {
existing: "cfloat",
details: Details::AutoImport {
path: "numpy",
name: "complex128",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "clongfloat"] => Some(Replacement {
existing: "clongfloat",
details: Details::AutoImport {
path: "numpy",
name: "clongdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "compat"] => Some(Replacement {
existing: "compat",
details: Details::Manual {
guideline: Some("Python 2 is no longer supported."),
},
}),
["numpy", "complex_"] => Some(Replacement {
existing: "complex_",
details: Details::AutoImport {
path: "numpy",
name: "complex128",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "DataSource"] => Some(Replacement {
existing: "DataSource",
details: Details::AutoImport {
path: "numpy.lib.npyio",
name: "DataSource",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "deprecate"] => Some(Replacement {
existing: "deprecate",
details: Details::Manual {
guideline: Some("Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`."),
},
}),
["numpy", "deprecate_with_doc"] => Some(Replacement {
existing: "deprecate_with_doc",
details: Details::Manual {
guideline: Some("Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`."),
},
}),
["numpy", "disp"] => Some(Replacement {
existing: "disp",
details: Details::Manual {
guideline: Some("Use a dedicated print function instead."),
},
}),
["numpy", "fastCopyAndTranspose"] => Some(Replacement {
existing: "fastCopyAndTranspose",
details: Details::Manual {
guideline: Some("Use `arr.T.copy()` instead."),
},
}),
["numpy", "find_common_type"] => Some(Replacement {
existing: "find_common_type",
details: Details::Manual {
guideline: Some("Use `numpy.promote_types` or `numpy.result_type` instead. To achieve semantics for the `scalar_types` argument, use `numpy.result_type` and pass the Python values `0`, `0.0`, or `0j`."),
},
}),
["numpy", "get_array_wrap"] => Some(Replacement {
existing: "get_array_wrap",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "float_"] => Some(Replacement {
existing: "float_",
details: Details::AutoImport {
path: "numpy",
name: "float64",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "geterrobj"] => Some(Replacement {
existing: "geterrobj",
details: Details::Manual {
guideline: Some("Use the `np.errstate` context manager instead."),
},
}),
["numpy", "INF"] => Some(Replacement {
existing: "INF",
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "Inf"] => Some(Replacement {
existing: "Inf",
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "Infinity"] => Some(Replacement {
existing: "Infinity",
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "infty"] => Some(Replacement {
existing: "infty",
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "issctype"] => Some(Replacement {
existing: "issctype",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "issubclass_"] => Some(Replacement {
existing: "issubclass_",
details: Details::AutoPurePython {
python_expr: "issubclass",
},
}),
["numpy", "issubsctype"] => Some(Replacement {
existing: "issubsctype",
details: Details::AutoImport {
path: "numpy",
name: "issubdtype",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "mat"] => Some(Replacement {
existing: "mat",
details: Details::AutoImport {
path: "numpy",
name: "asmatrix",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "maximum_sctype"] => Some(Replacement {
existing: "maximum_sctype",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "NaN"] => Some(Replacement {
existing: "NaN",
details: Details::AutoImport {
path: "numpy",
name: "nan",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "nbytes"] => Some(Replacement {
existing: "nbytes",
details: Details::Manual {
guideline: Some("Use `np.dtype(<dtype>).itemsize` instead."),
},
}),
["numpy", "NINF"] => Some(Replacement {
existing: "NINF",
details: Details::AutoPurePython {
python_expr: "-np.inf",
},
}),
["numpy", "NZERO"] => Some(Replacement {
existing: "NZERO",
details: Details::AutoPurePython {
python_expr: "-0.0",
},
}),
["numpy", "longcomplex"] => Some(Replacement {
existing: "longcomplex",
details: Details::AutoImport {
path: "numpy",
name: "clongdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "longfloat"] => Some(Replacement {
existing: "longfloat",
details: Details::AutoImport {
path: "numpy",
name: "longdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "lookfor"] => Some(Replacement {
existing: "lookfor",
details: Details::Manual {
guideline: Some("Search NumPys documentation directly."),
},
}),
["numpy", "obj2sctype"] => Some(Replacement {
existing: "obj2sctype",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "PINF"] => Some(Replacement {
existing: "PINF",
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "PZERO"] => Some(Replacement {
existing: "PZERO",
details: Details::AutoPurePython { python_expr: "0.0" },
}),
["numpy", "recfromcsv"] => Some(Replacement {
existing: "recfromcsv",
details: Details::Manual {
guideline: Some("Use `np.genfromtxt` with comma delimiter instead."),
},
}),
["numpy", "recfromtxt"] => Some(Replacement {
existing: "recfromtxt",
details: Details::Manual {
guideline: Some("Use `np.genfromtxt` instead."),
},
}),
["numpy", "round_"] => Some(Replacement {
existing: "round_",
details: Details::AutoImport {
path: "numpy",
name: "round",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "safe_eval"] => Some(Replacement {
existing: "safe_eval",
details: Details::AutoImport {
path: "ast",
name: "literal_eval",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "sctype2char"] => Some(Replacement {
existing: "sctype2char",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "sctypes"] => Some(Replacement {
existing: "sctypes",
details: Details::Manual {
guideline: None,
},
}),
["numpy", "seterrobj"] => Some(Replacement {
existing: "seterrobj",
details: Details::Manual {
guideline: Some("Use the `np.errstate` context manager instead."),
},
}),
["numpy", "set_string_function"] => Some(Replacement {
existing: "set_string_function",
details: Details::Manual {
guideline: Some("Use `np.set_printoptions` for custom printing of NumPy objects."),
},
}),
["numpy", "singlecomplex"] => Some(Replacement {
existing: "singlecomplex",
details: Details::AutoImport {
path: "numpy",
name: "complex64",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "string_"] => Some(Replacement {
existing: "string_",
details: Details::AutoImport {
path: "numpy",
name: "bytes_",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "source"] => Some(Replacement {
existing: "source",
details: Details::AutoImport {
path: "inspect",
name: "getsource",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "tracemalloc_domain"] => Some(Replacement {
existing: "tracemalloc_domain",
details: Details::AutoImport {
path: "numpy.lib",
name: "tracemalloc_domain",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "unicode_"] => Some(Replacement {
existing: "unicode_",
details: Details::AutoImport {
path: "numpy",
name: "str_",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "who"] => Some(Replacement {
existing: "who",
details: Details::Manual {
guideline: Some("Use an IDE variable explorer or `locals()` instead."),
},
}),
_ => None,
});
if let Some(replacement) = maybe_replacement {
let mut diagnostic = Diagnostic::new(
Numpy2Deprecation {
existing: replacement.existing.to_string(),
migration_guide: replacement.details.guideline(),
code_action: replacement.details.code_action(),
},
expr.range(),
);
match replacement.details {
Details::AutoImport {
path,
name,
compatibility,
} => {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(path, name),
expr.start(),
checker.semantic(),
)?;
let replacement_edit = Edit::range_replacement(binding, expr.range());
Ok(match compatibility {
Compatibility::BackwardsCompatible => {
Fix::safe_edits(import_edit, [replacement_edit])
}
Compatibility::Breaking => {
Fix::unsafe_edits(import_edit, [replacement_edit])
}
})
});
}
Details::AutoPurePython { python_expr } => diagnostic.set_fix(Fix::safe_edit(
Edit::range_replacement(python_expr.to_string(), expr.range()),
)),
Details::Manual { guideline: _ } => {}
};
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -1,848 +0,0 @@
---
source: crates/ruff_linter/src/rules/numpy/mod.rs
---
NPY201.py:4:5: NPY201 [*] `np.add_docstring` will be removed in NumPy 2.0. Use `numpy.lib.add_docstring` instead.
|
2 | import numpy as np
3 |
4 | np.add_docstring
| ^^^^^^^^^^^^^^^^ NPY201
5 |
6 | np.add_newdoc
|
= help: Replace with `numpy.lib.add_docstring`
Fix
1 |+from numpy.lib import add_docstring
1 2 | def func():
2 3 | import numpy as np
3 4 |
4 |- np.add_docstring
5 |+ add_docstring
5 6 |
6 7 | np.add_newdoc
7 8 |
NPY201.py:6:5: NPY201 [*] `np.add_newdoc` will be removed in NumPy 2.0. Use `numpy.lib.add_newdoc` instead.
|
4 | np.add_docstring
5 |
6 | np.add_newdoc
| ^^^^^^^^^^^^^ NPY201
7 |
8 | np.add_newdoc_ufunc
|
= help: Replace with `numpy.lib.add_newdoc`
Fix
1 |+from numpy.lib import add_newdoc
1 2 | def func():
2 3 | import numpy as np
3 4 |
4 5 | np.add_docstring
5 6 |
6 |- np.add_newdoc
7 |+ add_newdoc
7 8 |
8 9 | np.add_newdoc_ufunc
9 10 |
NPY201.py:8:5: NPY201 `np.add_newdoc_ufunc` will be removed in NumPy 2.0. `add_newdoc_ufunc` is an internal function.
|
6 | np.add_newdoc
7 |
8 | np.add_newdoc_ufunc
| ^^^^^^^^^^^^^^^^^^^ NPY201
9 |
10 | np.asfarray([1,2,3])
|
NPY201.py:10:5: NPY201 `np.asfarray` will be removed in NumPy 2.0. Use `np.asarray` with a `float` dtype instead.
|
8 | np.add_newdoc_ufunc
9 |
10 | np.asfarray([1,2,3])
| ^^^^^^^^^^^ NPY201
11 |
12 | np.byte_bounds(np.array([1,2,3]))
|
NPY201.py:12:5: NPY201 [*] `np.byte_bounds` will be removed in NumPy 2.0. Use `numpy.lib.array_utils.byte_bounds` on NumPy 2.0, or ignore this warning on earlier versions.
|
10 | np.asfarray([1,2,3])
11 |
12 | np.byte_bounds(np.array([1,2,3]))
| ^^^^^^^^^^^^^^ NPY201
13 |
14 | np.cast
|
= help: Replace with `numpy.lib.array_utils.byte_bounds` (requires NumPy 2.0 or greater)
Suggested fix
1 |+from numpy.lib.array_utils import byte_bounds
1 2 | def func():
2 3 | import numpy as np
3 4 |
--------------------------------------------------------------------------------
9 10 |
10 11 | np.asfarray([1,2,3])
11 12 |
12 |- np.byte_bounds(np.array([1,2,3]))
13 |+ byte_bounds(np.array([1,2,3]))
13 14 |
14 15 | np.cast
15 16 |
NPY201.py:14:5: NPY201 `np.cast` will be removed in NumPy 2.0. Use `np.asarray(arr, dtype=dtype)` instead.
|
12 | np.byte_bounds(np.array([1,2,3]))
13 |
14 | np.cast
| ^^^^^^^ NPY201
15 |
16 | np.cfloat(12+34j)
|
NPY201.py:16:5: NPY201 [*] `np.cfloat` will be removed in NumPy 2.0. Use `numpy.complex128` instead.
|
14 | np.cast
15 |
16 | np.cfloat(12+34j)
| ^^^^^^^^^ NPY201
17 |
18 | np.clongfloat(12+34j)
|
= help: Replace with `numpy.complex128`
Fix
13 13 |
14 14 | np.cast
15 15 |
16 |- np.cfloat(12+34j)
16 |+ np.complex128(12+34j)
17 17 |
18 18 | np.clongfloat(12+34j)
19 19 |
NPY201.py:18:5: NPY201 [*] `np.clongfloat` will be removed in NumPy 2.0. Use `numpy.clongdouble` instead.
|
16 | np.cfloat(12+34j)
17 |
18 | np.clongfloat(12+34j)
| ^^^^^^^^^^^^^ NPY201
19 |
20 | np.compat
|
= help: Replace with `numpy.clongdouble`
Fix
15 15 |
16 16 | np.cfloat(12+34j)
17 17 |
18 |- np.clongfloat(12+34j)
18 |+ np.clongdouble(12+34j)
19 19 |
20 20 | np.compat
21 21 |
NPY201.py:20:5: NPY201 `np.compat` will be removed in NumPy 2.0. Python 2 is no longer supported.
|
18 | np.clongfloat(12+34j)
19 |
20 | np.compat
| ^^^^^^^^^ NPY201
21 |
22 | np.complex_(12+34j)
|
NPY201.py:22:5: NPY201 [*] `np.complex_` will be removed in NumPy 2.0. Use `numpy.complex128` instead.
|
20 | np.compat
21 |
22 | np.complex_(12+34j)
| ^^^^^^^^^^^ NPY201
23 |
24 | np.DataSource
|
= help: Replace with `numpy.complex128`
Fix
19 19 |
20 20 | np.compat
21 21 |
22 |- np.complex_(12+34j)
22 |+ np.complex128(12+34j)
23 23 |
24 24 | np.DataSource
25 25 |
NPY201.py:24:5: NPY201 [*] `np.DataSource` will be removed in NumPy 2.0. Use `numpy.lib.npyio.DataSource` instead.
|
22 | np.complex_(12+34j)
23 |
24 | np.DataSource
| ^^^^^^^^^^^^^ NPY201
25 |
26 | np.deprecate
|
= help: Replace with `numpy.lib.npyio.DataSource`
Fix
1 |+from numpy.lib.npyio import DataSource
1 2 | def func():
2 3 | import numpy as np
3 4 |
--------------------------------------------------------------------------------
21 22 |
22 23 | np.complex_(12+34j)
23 24 |
24 |- np.DataSource
25 |+ DataSource
25 26 |
26 27 | np.deprecate
27 28 |
NPY201.py:26:5: NPY201 `np.deprecate` will be removed in NumPy 2.0. Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`.
|
24 | np.DataSource
25 |
26 | np.deprecate
| ^^^^^^^^^^^^ NPY201
27 |
28 | np.deprecate_with_doc
|
NPY201.py:28:5: NPY201 `np.deprecate_with_doc` will be removed in NumPy 2.0. Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`.
|
26 | np.deprecate
27 |
28 | np.deprecate_with_doc
| ^^^^^^^^^^^^^^^^^^^^^ NPY201
29 |
30 | np.disp(10)
|
NPY201.py:30:5: NPY201 `np.disp` will be removed in NumPy 2.0. Use a dedicated print function instead.
|
28 | np.deprecate_with_doc
29 |
30 | np.disp(10)
| ^^^^^^^ NPY201
31 |
32 | np.fastCopyAndTranspose
|
NPY201.py:32:5: NPY201 `np.fastCopyAndTranspose` will be removed in NumPy 2.0. Use `arr.T.copy()` instead.
|
30 | np.disp(10)
31 |
32 | np.fastCopyAndTranspose
| ^^^^^^^^^^^^^^^^^^^^^^^ NPY201
33 |
34 | np.find_common_type
|
NPY201.py:34:5: NPY201 `np.find_common_type` will be removed in NumPy 2.0. Use `numpy.promote_types` or `numpy.result_type` instead. To achieve semantics for the `scalar_types` argument, use `numpy.result_type` and pass the Python values `0`, `0.0`, or `0j`.
|
32 | np.fastCopyAndTranspose
33 |
34 | np.find_common_type
| ^^^^^^^^^^^^^^^^^^^ NPY201
35 |
36 | np.get_array_wrap
|
NPY201.py:36:5: NPY201 `np.get_array_wrap` will be removed without replacement in NumPy 2.0
|
34 | np.find_common_type
35 |
36 | np.get_array_wrap
| ^^^^^^^^^^^^^^^^^ NPY201
37 |
38 | np.float_
|
NPY201.py:38:5: NPY201 [*] `np.float_` will be removed in NumPy 2.0. Use `numpy.float64` instead.
|
36 | np.get_array_wrap
37 |
38 | np.float_
| ^^^^^^^^^ NPY201
39 |
40 | np.geterrobj
|
= help: Replace with `numpy.float64`
Fix
35 35 |
36 36 | np.get_array_wrap
37 37 |
38 |- np.float_
38 |+ np.float64
39 39 |
40 40 | np.geterrobj
41 41 |
NPY201.py:40:5: NPY201 `np.geterrobj` will be removed in NumPy 2.0. Use the `np.errstate` context manager instead.
|
38 | np.float_
39 |
40 | np.geterrobj
| ^^^^^^^^^^^^ NPY201
41 |
42 | np.Inf
|
NPY201.py:42:5: NPY201 [*] `np.Inf` will be removed in NumPy 2.0. Use `numpy.inf` instead.
|
40 | np.geterrobj
41 |
42 | np.Inf
| ^^^^^^ NPY201
43 |
44 | np.Infinity
|
= help: Replace with `numpy.inf`
Fix
39 39 |
40 40 | np.geterrobj
41 41 |
42 |- np.Inf
42 |+ np.inf
43 43 |
44 44 | np.Infinity
45 45 |
NPY201.py:44:5: NPY201 [*] `np.Infinity` will be removed in NumPy 2.0. Use `numpy.inf` instead.
|
42 | np.Inf
43 |
44 | np.Infinity
| ^^^^^^^^^^^ NPY201
45 |
46 | np.infty
|
= help: Replace with `numpy.inf`
Fix
41 41 |
42 42 | np.Inf
43 43 |
44 |- np.Infinity
44 |+ np.inf
45 45 |
46 46 | np.infty
47 47 |
NPY201.py:46:5: NPY201 [*] `np.infty` will be removed in NumPy 2.0. Use `numpy.inf` instead.
|
44 | np.Infinity
45 |
46 | np.infty
| ^^^^^^^^ NPY201
47 |
48 | np.issctype
|
= help: Replace with `numpy.inf`
Fix
43 43 |
44 44 | np.Infinity
45 45 |
46 |- np.infty
46 |+ np.inf
47 47 |
48 48 | np.issctype
49 49 |
NPY201.py:48:5: NPY201 `np.issctype` will be removed without replacement in NumPy 2.0
|
46 | np.infty
47 |
48 | np.issctype
| ^^^^^^^^^^^ NPY201
49 |
50 | np.issubclass_(np.int32, np.integer)
|
NPY201.py:50:5: NPY201 [*] `np.issubclass_` will be removed in NumPy 2.0. Use `issubclass` instead.
|
48 | np.issctype
49 |
50 | np.issubclass_(np.int32, np.integer)
| ^^^^^^^^^^^^^^ NPY201
51 |
52 | np.issubsctype
|
= help: Replace with `issubclass`
Fix
47 47 |
48 48 | np.issctype
49 49 |
50 |- np.issubclass_(np.int32, np.integer)
50 |+ issubclass(np.int32, np.integer)
51 51 |
52 52 | np.issubsctype
53 53 |
NPY201.py:52:5: NPY201 [*] `np.issubsctype` will be removed in NumPy 2.0. Use `numpy.issubdtype` instead.
|
50 | np.issubclass_(np.int32, np.integer)
51 |
52 | np.issubsctype
| ^^^^^^^^^^^^^^ NPY201
53 |
54 | np.mat
|
= help: Replace with `numpy.issubdtype`
Fix
49 49 |
50 50 | np.issubclass_(np.int32, np.integer)
51 51 |
52 |- np.issubsctype
52 |+ np.issubdtype
53 53 |
54 54 | np.mat
55 55 |
NPY201.py:54:5: NPY201 [*] `np.mat` will be removed in NumPy 2.0. Use `numpy.asmatrix` instead.
|
52 | np.issubsctype
53 |
54 | np.mat
| ^^^^^^ NPY201
55 |
56 | np.maximum_sctype
|
= help: Replace with `numpy.asmatrix`
Fix
51 51 |
52 52 | np.issubsctype
53 53 |
54 |- np.mat
54 |+ np.asmatrix
55 55 |
56 56 | np.maximum_sctype
57 57 |
NPY201.py:56:5: NPY201 `np.maximum_sctype` will be removed without replacement in NumPy 2.0
|
54 | np.mat
55 |
56 | np.maximum_sctype
| ^^^^^^^^^^^^^^^^^ NPY201
57 |
58 | np.NaN
|
NPY201.py:58:5: NPY201 [*] `np.NaN` will be removed in NumPy 2.0. Use `numpy.nan` instead.
|
56 | np.maximum_sctype
57 |
58 | np.NaN
| ^^^^^^ NPY201
59 |
60 | np.nbytes[np.int64]
|
= help: Replace with `numpy.nan`
Fix
55 55 |
56 56 | np.maximum_sctype
57 57 |
58 |- np.NaN
58 |+ np.nan
59 59 |
60 60 | np.nbytes[np.int64]
61 61 |
NPY201.py:60:5: NPY201 `np.nbytes` will be removed in NumPy 2.0. Use `np.dtype(<dtype>).itemsize` instead.
|
58 | np.NaN
59 |
60 | np.nbytes[np.int64]
| ^^^^^^^^^ NPY201
61 |
62 | np.NINF
|
NPY201.py:62:5: NPY201 [*] `np.NINF` will be removed in NumPy 2.0. Use `-np.inf` instead.
|
60 | np.nbytes[np.int64]
61 |
62 | np.NINF
| ^^^^^^^ NPY201
63 |
64 | np.NZERO
|
= help: Replace with `-np.inf`
Fix
59 59 |
60 60 | np.nbytes[np.int64]
61 61 |
62 |- np.NINF
62 |+ -np.inf
63 63 |
64 64 | np.NZERO
65 65 |
NPY201.py:64:5: NPY201 [*] `np.NZERO` will be removed in NumPy 2.0. Use `-0.0` instead.
|
62 | np.NINF
63 |
64 | np.NZERO
| ^^^^^^^^ NPY201
65 |
66 | np.longcomplex(12+34j)
|
= help: Replace with `-0.0`
Fix
61 61 |
62 62 | np.NINF
63 63 |
64 |- np.NZERO
64 |+ -0.0
65 65 |
66 66 | np.longcomplex(12+34j)
67 67 |
NPY201.py:66:5: NPY201 [*] `np.longcomplex` will be removed in NumPy 2.0. Use `numpy.clongdouble` instead.
|
64 | np.NZERO
65 |
66 | np.longcomplex(12+34j)
| ^^^^^^^^^^^^^^ NPY201
67 |
68 | np.longfloat(12+34j)
|
= help: Replace with `numpy.clongdouble`
Fix
63 63 |
64 64 | np.NZERO
65 65 |
66 |- np.longcomplex(12+34j)
66 |+ np.clongdouble(12+34j)
67 67 |
68 68 | np.longfloat(12+34j)
69 69 |
NPY201.py:68:5: NPY201 [*] `np.longfloat` will be removed in NumPy 2.0. Use `numpy.longdouble` instead.
|
66 | np.longcomplex(12+34j)
67 |
68 | np.longfloat(12+34j)
| ^^^^^^^^^^^^ NPY201
69 |
70 | np.lookfor
|
= help: Replace with `numpy.longdouble`
Fix
65 65 |
66 66 | np.longcomplex(12+34j)
67 67 |
68 |- np.longfloat(12+34j)
68 |+ np.longdouble(12+34j)
69 69 |
70 70 | np.lookfor
71 71 |
NPY201.py:70:5: NPY201 `np.lookfor` will be removed in NumPy 2.0. Search NumPys documentation directly.
|
68 | np.longfloat(12+34j)
69 |
70 | np.lookfor
| ^^^^^^^^^^ NPY201
71 |
72 | np.obj2sctype(int)
|
NPY201.py:72:5: NPY201 `np.obj2sctype` will be removed without replacement in NumPy 2.0
|
70 | np.lookfor
71 |
72 | np.obj2sctype(int)
| ^^^^^^^^^^^^^ NPY201
73 |
74 | np.PINF
|
NPY201.py:74:5: NPY201 [*] `np.PINF` will be removed in NumPy 2.0. Use `numpy.inf` instead.
|
72 | np.obj2sctype(int)
73 |
74 | np.PINF
| ^^^^^^^ NPY201
75 |
76 | np.PZERO
|
= help: Replace with `numpy.inf`
Fix
71 71 |
72 72 | np.obj2sctype(int)
73 73 |
74 |- np.PINF
74 |+ np.inf
75 75 |
76 76 | np.PZERO
77 77 |
NPY201.py:76:5: NPY201 [*] `np.PZERO` will be removed in NumPy 2.0. Use `0.0` instead.
|
74 | np.PINF
75 |
76 | np.PZERO
| ^^^^^^^^ NPY201
77 |
78 | np.recfromcsv
|
= help: Replace with `0.0`
Fix
73 73 |
74 74 | np.PINF
75 75 |
76 |- np.PZERO
76 |+ 0.0
77 77 |
78 78 | np.recfromcsv
79 79 |
NPY201.py:78:5: NPY201 `np.recfromcsv` will be removed in NumPy 2.0. Use `np.genfromtxt` with comma delimiter instead.
|
76 | np.PZERO
77 |
78 | np.recfromcsv
| ^^^^^^^^^^^^^ NPY201
79 |
80 | np.recfromtxt
|
NPY201.py:80:5: NPY201 `np.recfromtxt` will be removed in NumPy 2.0. Use `np.genfromtxt` instead.
|
78 | np.recfromcsv
79 |
80 | np.recfromtxt
| ^^^^^^^^^^^^^ NPY201
81 |
82 | np.round_(12.34)
|
NPY201.py:82:5: NPY201 [*] `np.round_` will be removed in NumPy 2.0. Use `numpy.round` instead.
|
80 | np.recfromtxt
81 |
82 | np.round_(12.34)
| ^^^^^^^^^ NPY201
83 |
84 | np.safe_eval
|
= help: Replace with `numpy.round`
Fix
79 79 |
80 80 | np.recfromtxt
81 81 |
82 |- np.round_(12.34)
82 |+ np.round(12.34)
83 83 |
84 84 | np.safe_eval
85 85 |
NPY201.py:84:5: NPY201 [*] `np.safe_eval` will be removed in NumPy 2.0. Use `ast.literal_eval` instead.
|
82 | np.round_(12.34)
83 |
84 | np.safe_eval
| ^^^^^^^^^^^^ NPY201
85 |
86 | np.sctype2char
|
= help: Replace with `ast.literal_eval`
Fix
1 |+from ast import literal_eval
1 2 | def func():
2 3 | import numpy as np
3 4 |
--------------------------------------------------------------------------------
81 82 |
82 83 | np.round_(12.34)
83 84 |
84 |- np.safe_eval
85 |+ literal_eval
85 86 |
86 87 | np.sctype2char
87 88 |
NPY201.py:86:5: NPY201 `np.sctype2char` will be removed without replacement in NumPy 2.0
|
84 | np.safe_eval
85 |
86 | np.sctype2char
| ^^^^^^^^^^^^^^ NPY201
87 |
88 | np.sctypes
|
NPY201.py:88:5: NPY201 `np.sctypes` will be removed without replacement in NumPy 2.0
|
86 | np.sctype2char
87 |
88 | np.sctypes
| ^^^^^^^^^^ NPY201
89 |
90 | np.seterrobj
|
NPY201.py:90:5: NPY201 `np.seterrobj` will be removed in NumPy 2.0. Use the `np.errstate` context manager instead.
|
88 | np.sctypes
89 |
90 | np.seterrobj
| ^^^^^^^^^^^^ NPY201
91 |
92 | np.set_numeric_ops
|
NPY201.py:94:5: NPY201 `np.set_string_function` will be removed in NumPy 2.0. Use `np.set_printoptions` for custom printing of NumPy objects.
|
92 | np.set_numeric_ops
93 |
94 | np.set_string_function
| ^^^^^^^^^^^^^^^^^^^^^^ NPY201
95 |
96 | np.singlecomplex(12+1j)
|
NPY201.py:96:5: NPY201 [*] `np.singlecomplex` will be removed in NumPy 2.0. Use `numpy.complex64` instead.
|
94 | np.set_string_function
95 |
96 | np.singlecomplex(12+1j)
| ^^^^^^^^^^^^^^^^ NPY201
97 |
98 | np.string_("asdf")
|
= help: Replace with `numpy.complex64`
Fix
93 93 |
94 94 | np.set_string_function
95 95 |
96 |- np.singlecomplex(12+1j)
96 |+ np.complex64(12+1j)
97 97 |
98 98 | np.string_("asdf")
99 99 |
NPY201.py:98:5: NPY201 [*] `np.string_` will be removed in NumPy 2.0. Use `numpy.bytes_` instead.
|
96 | np.singlecomplex(12+1j)
97 |
98 | np.string_("asdf")
| ^^^^^^^^^^ NPY201
99 |
100 | np.source
|
= help: Replace with `numpy.bytes_`
Fix
95 95 |
96 96 | np.singlecomplex(12+1j)
97 97 |
98 |- np.string_("asdf")
98 |+ np.bytes_("asdf")
99 99 |
100 100 | np.source
101 101 |
NPY201.py:100:5: NPY201 [*] `np.source` will be removed in NumPy 2.0. Use `inspect.getsource` instead.
|
98 | np.string_("asdf")
99 |
100 | np.source
| ^^^^^^^^^ NPY201
101 |
102 | np.tracemalloc_domain
|
= help: Replace with `inspect.getsource`
Fix
1 |+from inspect import getsource
1 2 | def func():
2 3 | import numpy as np
3 4 |
--------------------------------------------------------------------------------
97 98 |
98 99 | np.string_("asdf")
99 100 |
100 |- np.source
101 |+ getsource
101 102 |
102 103 | np.tracemalloc_domain
103 104 |
NPY201.py:102:5: NPY201 [*] `np.tracemalloc_domain` will be removed in NumPy 2.0. Use `numpy.lib.tracemalloc_domain` instead.
|
100 | np.source
101 |
102 | np.tracemalloc_domain
| ^^^^^^^^^^^^^^^^^^^^^ NPY201
103 |
104 | np.unicode_("asf")
|
= help: Replace with `numpy.lib.tracemalloc_domain`
Fix
1 |+from numpy.lib import tracemalloc_domain
1 2 | def func():
2 3 | import numpy as np
3 4 |
--------------------------------------------------------------------------------
99 100 |
100 101 | np.source
101 102 |
102 |- np.tracemalloc_domain
103 |+ tracemalloc_domain
103 104 |
104 105 | np.unicode_("asf")
105 106 |
NPY201.py:104:5: NPY201 [*] `np.unicode_` will be removed in NumPy 2.0. Use `numpy.str_` instead.
|
102 | np.tracemalloc_domain
103 |
104 | np.unicode_("asf")
| ^^^^^^^^^^^ NPY201
105 |
106 | np.who()
|
= help: Replace with `numpy.str_`
Fix
101 101 |
102 102 | np.tracemalloc_domain
103 103 |
104 |- np.unicode_("asf")
104 |+ np.str_("asf")
105 105 |
106 106 | np.who()
NPY201.py:106:5: NPY201 `np.who` will be removed in NumPy 2.0. Use an IDE variable explorer or `locals()` instead.
|
104 | np.unicode_("asf")
105 |
106 | np.who()
| ^^^^^^ NPY201
|

View File

@@ -4,6 +4,7 @@ use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_dict;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -128,16 +129,22 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
}
// Exclude non-dictionary value.
let Some(name) = subscript_value.as_name_expr() else {
return;
};
let Some(binding) = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
let Expr::Name(ast::ExprName {
id: subscript_name, ..
}) = subscript_value.as_ref()
else {
return;
};
let scope = checker.semantic().current_scope();
let bindings: Vec<&Binding> = scope
.get_all(subscript_name)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return;
};
if !is_dict(binding, checker.semantic()) {
return;
}
@@ -158,7 +165,8 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
// ```
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
ComparableExpr::from(expr) == ComparableExpr::from(name)
expr.as_name_expr()
.is_some_and(|expr| expr.id == *subscript_name)
})
}) {
return;

View File

@@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -143,16 +144,20 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
}
// Avoid non-list values.
let Some(name) = value.as_name_expr() else {
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
return;
};
let Some(binding) = checker
let bindings: Vec<&Binding> = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
.current_scope()
.get_all(id)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return;
};
if !is_list(binding, checker.semantic()) {
return;
}
@@ -171,12 +176,15 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
// ```python
// filtered = [x for x in y if x in filtered]
// ```
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr().is_some_and(|expr| expr.id == name.id)
})
}) {
return;
if let Some(value_name) = value.as_name_expr() {
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr()
.is_some_and(|expr| expr.id == value_name.id)
})
}) {
return;
}
}
checker

View File

@@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -101,16 +102,20 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
}
// Avoid non-list values.
let Some(name) = value.as_name_expr() else {
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
return;
};
let Some(binding) = checker
let bindings: Vec<&Binding> = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
.current_scope()
.get_all(id)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return;
};
if !is_list(binding, checker.semantic()) {
return;
}

View File

@@ -24,7 +24,7 @@ use ruff_source_file::Locator;
/// if foo == "blah":
/// do_blah_thing()
/// ```
///
/// [PEP 8]: https://peps.python.org/pep-0008/#other-recommendations
#[violation]
pub struct MultipleStatementsOnOneLineColon;
@@ -206,13 +206,12 @@ pub(crate) fn compound_statements(
{
colon = Some((range.start(), range.end()));
// Allow `class C: ...`-style definitions.
allow_ellipsis = true;
// Allow `class C: ...`-style definitions in stubs.
allow_ellipsis = class.is_some();
}
}
Tok::Semi => {
semi = Some((range.start(), range.end()));
allow_ellipsis = false;
}
Tok::Comment(..) | Tok::Indent | Tok::Dedent | Tok::NonLogicalNewline => {}
_ => {
@@ -224,7 +223,6 @@ pub(crate) fn compound_statements(
// Reset.
semi = None;
allow_ellipsis = false;
}
if let Some((start, end)) = colon {
@@ -247,7 +245,6 @@ pub(crate) fn compound_statements(
try_ = None;
while_ = None;
with = None;
allow_ellipsis = false;
}
}
}

View File

@@ -138,7 +138,6 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
| "list"
| "dict"
| "set"
| "memoryview"
) && checker.semantic().is_builtin(id)
{
checker.diagnostics.push(Diagnostic::new(
@@ -198,98 +197,7 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool {
// Ex) `type(obj) == int`
matches!(
id.as_str(),
"bool"
| "bytearray"
| "bytes"
| "classmethod"
| "complex"
| "dict"
| "enumerate"
| "filter"
| "float"
| "frozenset"
| "int"
| "list"
| "map"
| "memoryview"
| "object"
| "property"
| "range"
| "reversed"
| "set"
| "slice"
| "staticmethod"
| "str"
| "super"
| "tuple"
| "type"
| "zip"
| "ArithmeticError"
| "AssertionError"
| "AttributeError"
| "BaseException"
| "BlockingIOError"
| "BrokenPipeError"
| "BufferError"
| "BytesWarning"
| "ChildProcessError"
| "ConnectionAbortedError"
| "ConnectionError"
| "ConnectionRefusedError"
| "ConnectionResetError"
| "DeprecationWarning"
| "EnvironmentError"
| "EOFError"
| "Exception"
| "FileExistsError"
| "FileNotFoundError"
| "FloatingPointError"
| "FutureWarning"
| "GeneratorExit"
| "ImportError"
| "ImportWarning"
| "IndentationError"
| "IndexError"
| "InterruptedError"
| "IOError"
| "IsADirectoryError"
| "KeyboardInterrupt"
| "KeyError"
| "LookupError"
| "MemoryError"
| "ModuleNotFoundError"
| "NameError"
| "NotADirectoryError"
| "NotImplementedError"
| "OSError"
| "OverflowError"
| "PendingDeprecationWarning"
| "PermissionError"
| "ProcessLookupError"
| "RecursionError"
| "ReferenceError"
| "ResourceWarning"
| "RuntimeError"
| "RuntimeWarning"
| "StopAsyncIteration"
| "StopIteration"
| "SyntaxError"
| "SyntaxWarning"
| "SystemError"
| "SystemExit"
| "TabError"
| "TimeoutError"
| "TypeError"
| "UnboundLocalError"
| "UnicodeDecodeError"
| "UnicodeEncodeError"
| "UnicodeError"
| "UnicodeTranslateError"
| "UnicodeWarning"
| "UserWarning"
| "ValueError"
| "Warning"
| "ZeroDivisionError"
"int" | "str" | "float" | "bool" | "complex" | "bytes" | "list" | "dict" | "set"
) && semantic.is_builtin(id)
}
_ => false,

View File

@@ -92,8 +92,6 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
70 | a = \
71 | 5;
| ^ E703
72 | #:
73 | with x(y) as z: ...
|
= help: Remove unnecessary semicolon
@@ -103,7 +101,5 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
70 70 | a = \
71 |- 5;
71 |+ 5
72 72 | #:
73 73 | with x(y) as z: ...

View File

@@ -17,154 +17,144 @@ E721.py:5:4: E721 Do not compare types, use `isinstance()`
5 | if type(res) != type(""):
| ^^^^^^^^^^^^^^^^^^^^^ E721
6 | pass
7 | #: E721
7 | #: Okay
|
E721.py:8:4: E721 Do not compare types, use `isinstance()`
E721.py:15:4: E721 Do not compare types, use `isinstance()`
|
6 | pass
7 | #: E721
8 | if type(res) == memoryview:
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
9 | pass
10 | #: Okay
|
E721.py:18:4: E721 Do not compare types, use `isinstance()`
|
16 | import types
17 |
18 | if type(res) is not types.ListType:
13 | import types
14 |
15 | if type(res) is not types.ListType:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
19 | pass
20 | #: E721
16 | pass
17 | #: E721
|
E721.py:21:8: E721 Do not compare types, use `isinstance()`
E721.py:18:8: E721 Do not compare types, use `isinstance()`
|
19 | pass
20 | #: E721
21 | assert type(res) == type(False) or type(res) == type(None)
16 | pass
17 | #: E721
18 | assert type(res) == type(False) or type(res) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
22 | #: E721
23 | assert type(res) == type([])
19 | #: E721
20 | assert type(res) == type([])
|
E721.py:23:8: E721 Do not compare types, use `isinstance()`
E721.py:20:8: E721 Do not compare types, use `isinstance()`
|
21 | assert type(res) == type(False) or type(res) == type(None)
22 | #: E721
23 | assert type(res) == type([])
18 | assert type(res) == type(False) or type(res) == type(None)
19 | #: E721
20 | assert type(res) == type([])
| ^^^^^^^^^^^^^^^^^^^^^ E721
24 | #: E721
25 | assert type(res) == type(())
21 | #: E721
22 | assert type(res) == type(())
|
E721.py:25:8: E721 Do not compare types, use `isinstance()`
E721.py:22:8: E721 Do not compare types, use `isinstance()`
|
23 | assert type(res) == type([])
24 | #: E721
25 | assert type(res) == type(())
20 | assert type(res) == type([])
21 | #: E721
22 | assert type(res) == type(())
| ^^^^^^^^^^^^^^^^^^^^^ E721
26 | #: E721
27 | assert type(res) == type((0,))
23 | #: E721
24 | assert type(res) == type((0,))
|
E721.py:27:8: E721 Do not compare types, use `isinstance()`
E721.py:24:8: E721 Do not compare types, use `isinstance()`
|
25 | assert type(res) == type(())
26 | #: E721
27 | assert type(res) == type((0,))
22 | assert type(res) == type(())
23 | #: E721
24 | assert type(res) == type((0,))
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
28 | #: E721
29 | assert type(res) == type((0))
25 | #: E721
26 | assert type(res) == type((0))
|
E721.py:29:8: E721 Do not compare types, use `isinstance()`
E721.py:26:8: E721 Do not compare types, use `isinstance()`
|
27 | assert type(res) == type((0,))
28 | #: E721
29 | assert type(res) == type((0))
24 | assert type(res) == type((0,))
25 | #: E721
26 | assert type(res) == type((0))
| ^^^^^^^^^^^^^^^^^^^^^^ E721
30 | #: E721
31 | assert type(res) != type((1, ))
27 | #: E721
28 | assert type(res) != type((1, ))
|
E721.py:31:8: E721 Do not compare types, use `isinstance()`
E721.py:28:8: E721 Do not compare types, use `isinstance()`
|
29 | assert type(res) == type((0))
30 | #: E721
31 | assert type(res) != type((1, ))
26 | assert type(res) == type((0))
27 | #: E721
28 | assert type(res) != type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
32 | #: Okay
33 | assert type(res) is type((1, ))
29 | #: Okay
30 | assert type(res) is type((1, ))
|
E721.py:33:8: E721 Do not compare types, use `isinstance()`
E721.py:30:8: E721 Do not compare types, use `isinstance()`
|
31 | assert type(res) != type((1, ))
32 | #: Okay
33 | assert type(res) is type((1, ))
28 | assert type(res) != type((1, ))
29 | #: Okay
30 | assert type(res) is type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
34 | #: Okay
35 | assert type(res) is not type((1, ))
31 | #: Okay
32 | assert type(res) is not type((1, ))
|
E721.py:35:8: E721 Do not compare types, use `isinstance()`
E721.py:32:8: E721 Do not compare types, use `isinstance()`
|
33 | assert type(res) is type((1, ))
34 | #: Okay
35 | assert type(res) is not type((1, ))
30 | assert type(res) is type((1, ))
31 | #: Okay
32 | assert type(res) is not type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
|
E721.py:37:8: E721 Do not compare types, use `isinstance()`
E721.py:34:8: E721 Do not compare types, use `isinstance()`
|
35 | assert type(res) is not type((1, ))
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
32 | assert type(res) is not type((1, ))
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
|
E721.py:39:8: E721 Do not compare types, use `isinstance()`
E721.py:36:8: E721 Do not compare types, use `isinstance()`
|
37 | assert type(res) == type ([2, ])
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
34 | assert type(res) == type ([2, ])
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
|
E721.py:41:8: E721 Do not compare types, use `isinstance()`
E721.py:38:8: E721 Do not compare types, use `isinstance()`
|
39 | assert type(res) == type( ( ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
36 | assert type(res) == type( ( ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
42 | #:
39 | #:
|
E721.py:107:12: E721 Do not compare types, use `isinstance()`
E721.py:96:12: E721 Do not compare types, use `isinstance()`
|
94 | def asdf(self, value: str | None):
95 | #: E721
96 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
97 | ...
|
E721.py:106:12: E721 Do not compare types, use `isinstance()`
|
105 | def asdf(self, value: str | None):
106 | #: E721
107 | if type(value) is str:
104 | def asdf(self, value: str | None):
105 | #: E721
106 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
108 | ...
|
E721.py:117:12: E721 Do not compare types, use `isinstance()`
|
115 | def asdf(self, value: str | None):
116 | #: E721
117 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
118 | ...
107 | ...
|

View File

@@ -17,116 +17,96 @@ E721.py:5:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()`
5 | if type(res) != type(""):
| ^^^^^^^^^^^^^^^^^^^^^ E721
6 | pass
7 | #: E721
7 | #: Okay
|
E721.py:8:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:18:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
6 | pass
7 | #: E721
8 | if type(res) == memoryview:
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
9 | pass
10 | #: Okay
|
E721.py:21:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
19 | pass
20 | #: E721
21 | assert type(res) == type(False) or type(res) == type(None)
16 | pass
17 | #: E721
18 | assert type(res) == type(False) or type(res) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
22 | #: E721
23 | assert type(res) == type([])
19 | #: E721
20 | assert type(res) == type([])
|
E721.py:23:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:20:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
21 | assert type(res) == type(False) or type(res) == type(None)
22 | #: E721
23 | assert type(res) == type([])
18 | assert type(res) == type(False) or type(res) == type(None)
19 | #: E721
20 | assert type(res) == type([])
| ^^^^^^^^^^^^^^^^^^^^^ E721
24 | #: E721
25 | assert type(res) == type(())
21 | #: E721
22 | assert type(res) == type(())
|
E721.py:25:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:22:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
23 | assert type(res) == type([])
24 | #: E721
25 | assert type(res) == type(())
20 | assert type(res) == type([])
21 | #: E721
22 | assert type(res) == type(())
| ^^^^^^^^^^^^^^^^^^^^^ E721
26 | #: E721
27 | assert type(res) == type((0,))
23 | #: E721
24 | assert type(res) == type((0,))
|
E721.py:27:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:24:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
25 | assert type(res) == type(())
26 | #: E721
27 | assert type(res) == type((0,))
22 | assert type(res) == type(())
23 | #: E721
24 | assert type(res) == type((0,))
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
28 | #: E721
29 | assert type(res) == type((0))
25 | #: E721
26 | assert type(res) == type((0))
|
E721.py:29:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:26:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
27 | assert type(res) == type((0,))
28 | #: E721
29 | assert type(res) == type((0))
24 | assert type(res) == type((0,))
25 | #: E721
26 | assert type(res) == type((0))
| ^^^^^^^^^^^^^^^^^^^^^^ E721
30 | #: E721
31 | assert type(res) != type((1, ))
27 | #: E721
28 | assert type(res) != type((1, ))
|
E721.py:31:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:28:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
29 | assert type(res) == type((0))
30 | #: E721
31 | assert type(res) != type((1, ))
26 | assert type(res) == type((0))
27 | #: E721
28 | assert type(res) != type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
32 | #: Okay
33 | assert type(res) is type((1, ))
29 | #: Okay
30 | assert type(res) is type((1, ))
|
E721.py:37:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:34:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
35 | assert type(res) is not type((1, ))
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
32 | assert type(res) is not type((1, ))
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
|
E721.py:39:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:36:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
37 | assert type(res) == type ([2, ])
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
34 | assert type(res) == type ([2, ])
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
|
E721.py:41:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:38:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
39 | assert type(res) == type( ( ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
36 | assert type(res) == type( ( ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
42 | #:
|
E721.py:59:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
57 | pass
58 | #: E721
59 | if type(res) == type:
| ^^^^^^^^^^^^^^^^^ E721
60 | pass
61 | #: Okay
39 | #:
|

View File

@@ -1,6 +1,6 @@
use memchr::memchr_iter;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
@@ -46,16 +46,14 @@ use crate::docstrings::Docstring;
#[violation]
pub struct EscapeSequenceInDocstring;
impl Violation for EscapeSequenceInDocstring {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
impl AlwaysFixableViolation for EscapeSequenceInDocstring {
#[derive_message_formats]
fn message(&self) -> String {
format!(r#"Use `r"""` if any backslashes in a docstring"#)
}
fn fix_title(&self) -> Option<String> {
Some(format!(r#"Add `r` prefix"#))
fn fix_title(&self) -> String {
format!(r#"Add `r` prefix"#)
}
}
@@ -76,12 +74,10 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
}) {
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
if !docstring.leading_quote().contains(['u', 'U']) {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"r".to_owned() + docstring.contents,
docstring.range(),
)));
}
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"r".to_owned() + docstring.contents,
docstring.range(),
)));
checker.diagnostics.push(diagnostic);
}

View File

@@ -61,14 +61,12 @@ impl Violation for TripleSingleQuotes {
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let leading_quote = docstring.leading_quote();
let prefixes = leading_quote
let prefixes = docstring
.leading_quote()
.trim_end_matches(|c| c == '\'' || c == '"')
.to_owned();
let expected_quote = if docstring.body().contains("\"\"\"") {
if docstring.body().contains("\'\'\'") {
return;
}
Quote::Single
} else {
Quote::Double

View File

@@ -17,12 +17,4 @@ D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring
4 4 |
5 5 | def double_quotes_backslash_raw():
D301.py:37:5: D301 Use `r"""` if any backslashes in a docstring
|
36 | def shouldnt_add_raw_here2():
37 | u"Sum\\mary."
| ^^^^^^^^^^^^^ D301
|
= help: Add `r` prefix

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
assertion_line: 134
---
D300.py:6:5: D300 Use triple double quotes `"""`
|
@@ -24,8 +23,5 @@ D300.py:10:5: D300 [*] Use triple double quotes `"""`
9 9 | def contains_quote():
10 |- 'Sum"\\mary.'
10 |+ """Sum"\\mary."""
11 11 |
12 12 |
13 13 | # OK

View File

@@ -26,7 +26,6 @@ mod tests {
use crate::linter::{check_path, LinterResult};
use crate::registry::{AsRule, Linter, Rule};
use crate::rules::pyflakes;
use crate::settings::types::PreviewMode;
use crate::settings::{flags, LinterSettings};
use crate::source_kind::SourceKind;
use crate::test::{test_path, test_snippet};
@@ -146,7 +145,6 @@ mod tests {
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_2.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_3.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
#[test_case(Rule::UnusedAnnotation, Path::new("F842.py"))]
#[test_case(Rule::RaiseNotImplemented, Path::new("F901.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
@@ -159,24 +157,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let diagnostics = test_path(
@@ -1146,8 +1126,7 @@ mod tests {
#[test]
fn used_as_star_unpack() {
// In stable, starred names in unpack are used if RHS is not a tuple/list literal.
// In preview, these should be marked as unused.
// Star names in unpack are used if RHS is not a tuple/list literal.
flakes(
r#"
def f():

View File

@@ -12,7 +12,6 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::fix::edits::delete_stmt;
use crate::settings::types::PreviewMode;
/// ## What it does
/// Checks for the presence of unused variables in function scopes.
@@ -25,9 +24,6 @@ use crate::settings::types::PreviewMode;
/// prefixed with an underscore, or some other value that adheres to the
/// [`dummy-variable-rgx`] pattern.
///
/// Under [preview mode](https://docs.astral.sh/ruff/preview), this rule also
/// triggers on unused unpacked assignments (for example, `x, y = foo()`).
///
/// ## Example
/// ```python
/// def foo():
@@ -322,10 +318,7 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu
.bindings()
.map(|(name, binding_id)| (name, checker.semantic().binding(binding_id)))
.filter_map(|(name, binding)| {
if (binding.kind.is_assignment()
|| binding.kind.is_named_expr_assignment()
|| (matches!(checker.settings.preview, PreviewMode::Enabled)
&& binding.kind.is_unpacked_assignment()))
if (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment())
&& !binding.is_nonlocal()
&& !binding.is_global()
&& !binding.is_used()

View File

@@ -20,7 +20,7 @@ F841_1.py:6:8: F841 Local variable `y` is assigned to but never used
F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
|
15 | def f():
16 | (x, y) = coords = 1, 2
16 | (x, y) = coords = 1, 2 # this triggers F841 on coords
| ^^^^^^ F841
|
= help: Remove assignment to unused variable `coords`
@@ -29,8 +29,8 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
13 13 |
14 14 |
15 15 | def f():
16 |- (x, y) = coords = 1, 2
16 |+ (x, y) = 1, 2
16 |- (x, y) = coords = 1, 2 # this triggers F841 on coords
16 |+ (x, y) = 1, 2 # this triggers F841 on coords
17 17 |
18 18 |
19 19 | def f():
@@ -38,7 +38,7 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
|
19 | def f():
20 | coords = (x, y) = 1, 2
20 | coords = (x, y) = 1, 2 # this triggers F841 on coords
| ^^^^^^ F841
|
= help: Remove assignment to unused variable `coords`
@@ -47,8 +47,8 @@ F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
17 17 |
18 18 |
19 19 | def f():
20 |- coords = (x, y) = 1, 2
20 |+ (x, y) = 1, 2
20 |- coords = (x, y) = 1, 2 # this triggers F841 on coords
20 |+ (x, y) = 1, 2 # this triggers F841 on coords
21 21 |
22 22 |
23 23 | def f():

View File

@@ -1,23 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
11 | def bar():
12 | a = foo()
| ^ F841
13 | b, c = foo()
|
= help: Remove assignment to unused variable `a`
Suggested fix
9 9 |
10 10 |
11 11 | def bar():
12 |- a = foo()
12 |+ foo()
13 13 | b, c = foo()
14 14 |
15 15 |

View File

@@ -1,41 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
11 | def bar():
12 | a = foo()
| ^ F841
13 | b, c = foo()
|
= help: Remove assignment to unused variable `a`
Suggested fix
9 9 |
10 10 |
11 11 | def bar():
12 |- a = foo()
12 |+ foo()
13 13 | b, c = foo()
14 14 |
15 15 |
F841_4.py:13:5: F841 Local variable `b` is assigned to but never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ F841
|
= help: Remove assignment to unused variable `b`
F841_4.py:13:8: F841 Local variable `c` is assigned to but never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ F841
|
= help: Remove assignment to unused variable `c`

View File

@@ -119,23 +119,12 @@ fn collect_specs(formats: &[CFormatStrOrBytes<String>]) -> Vec<&CFormatSpec> {
/// Return `true` if the format string is equivalent to the constant type
fn equivalent(format: &CFormatSpec, value: &Expr) -> bool {
let format_type = FormatType::from(format.format_char);
let format = FormatType::from(format.format_char);
match ResolvedPythonType::from(value) {
ResolvedPythonType::Atom(atom) => {
// Special case where `%c` allows single character strings to be formatted
if format.format_char == 'c' {
if let Expr::StringLiteral(string) = value {
let mut chars = string.chars();
if chars.next().is_some() && chars.next().is_none() {
return true;
}
}
}
format_type.is_compatible_with(atom)
ResolvedPythonType::Atom(atom) => format.is_compatible_with(atom),
ResolvedPythonType::Union(atoms) => {
atoms.iter().all(|atom| format.is_compatible_with(*atom))
}
ResolvedPythonType::Union(atoms) => atoms
.iter()
.all(|atom| format_type.is_compatible_with(*atom)),
ResolvedPythonType::Unknown => true,
ResolvedPythonType::TypeError => true,
}

View File

@@ -60,7 +60,6 @@ mod tests {
#[test_case(Rule::ReplaceStdoutStderr, Path::new("UP022.py"))]
#[test_case(Rule::ReplaceUniversalNewlines, Path::new("UP021.py"))]
#[test_case(Rule::SuperCallWithParameters, Path::new("UP008.py"))]
#[test_case(Rule::TimeoutErrorAlias, Path::new("UP041.py"))]
#[test_case(Rule::TypeOfPrimitive, Path::new("UP003.py"))]
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
#[test_case(Rule::UTF8EncodingDeclaration, Path::new("UP009_0.py"))]

View File

@@ -181,9 +181,6 @@ fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> {
let [field, annotation] = elts.as_slice() else {
return None;
};
if annotation.is_starred_expr() {
return None;
}
let ast::ExprStringLiteral { value: field, .. } = field.as_string_literal_expr()?;
if !is_identifier(field) {
return None;

View File

@@ -191,21 +191,15 @@ fn try_convert_to_f_string(
summary: &mut FormatSummaryValues,
locator: &Locator,
) -> Result<Option<String>> {
let contents = locator.slice(range);
// Strip the unicode prefix. It's redundant in Python 3, and invalid when used
// with f-strings.
let contents = locator.slice(range);
let contents = if contents.starts_with('U') || contents.starts_with('u') {
&contents[1..]
} else {
contents
};
// Temporarily strip the raw prefix, if present. It will be prepended to the result, before the
// 'f', to match the prefix order both the Ruff formatter (and Black) use when formatting code.
let raw = contents.starts_with('R') || contents.starts_with('r');
let contents = if raw { &contents[1..] } else { contents };
// Remove the leading and trailing quotes.
let leading_quote = leading_quote(contents).context("Unable to identify leading quote")?;
let trailing_quote = trailing_quote(contents).context("Unable to identify trailing quote")?;
@@ -297,10 +291,7 @@ fn try_convert_to_f_string(
}
// Construct the format string.
let mut contents = String::with_capacity(usize::from(raw) + 1 + converted.len());
if raw {
contents.push('r');
}
let mut contents = String::with_capacity(1 + converted.len());
contents.push('f');
contents.push_str(leading_quote);
contents.push_str(&converted);

View File

@@ -20,7 +20,6 @@ pub(crate) use redundant_open_modes::*;
pub(crate) use replace_stdout_stderr::*;
pub(crate) use replace_universal_newlines::*;
pub(crate) use super_call_with_parameters::*;
pub(crate) use timeout_error_alias::*;
pub(crate) use type_of_primitive::*;
pub(crate) use typing_text_str_alias::*;
pub(crate) use unicode_kind_prefix::*;
@@ -60,7 +59,6 @@ mod redundant_open_modes;
mod replace_stdout_stderr;
mod replace_universal_newlines;
mod super_call_with_parameters;
mod timeout_error_alias;
mod type_of_primitive;
mod typing_text_str_alias;
mod unicode_kind_prefix;

View File

@@ -1,192 +0,0 @@
use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext};
use ruff_text_size::{Ranged, TextRange};
use crate::fix::edits::pad;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::compose_call_path;
use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker;
use crate::settings::types::PythonVersion;
/// ## What it does
/// Checks for uses of exceptions that alias `TimeoutError`.
///
/// ## Why is this bad?
/// `TimeoutError` is the builtin error type used for exceptions when a system
/// function timed out at the system level.
///
/// In Python 3.10, `socket.timeout` was aliased to `TimeoutError`. In Python
/// 3.11, `asyncio.TimeoutError` was aliased to `TimeoutError`.
///
/// These aliases remain in place for compatibility with older versions of
/// Python, but may be removed in future versions.
///
/// Prefer using `TimeoutError` directly, as it is more idiomatic and future-proof.
///
/// ## Example
/// ```python
/// raise asyncio.TimeoutError
/// ```
///
/// Use instead:
/// ```python
/// raise TimeoutError
/// ```
///
/// ## References
/// - [Python documentation: `TimeoutError`](https://docs.python.org/3/library/exceptions.html#TimeoutError)
#[violation]
pub struct TimeoutErrorAlias {
name: Option<String>,
}
impl AlwaysFixableViolation for TimeoutErrorAlias {
#[derive_message_formats]
fn message(&self) -> String {
format!("Replace aliased errors with `TimeoutError`")
}
fn fix_title(&self) -> String {
let TimeoutErrorAlias { name } = self;
match name {
None => "Replace with builtin `TimeoutError`".to_string(),
Some(name) => format!("Replace `{name}` with builtin `TimeoutError`"),
}
}
}
/// Return `true` if an [`Expr`] is an alias of `TimeoutError`.
fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion) -> bool {
semantic.resolve_call_path(expr).is_some_and(|call_path| {
if target_version >= PythonVersion::Py311 {
matches!(call_path.as_slice(), [""] | ["asyncio", "TimeoutError"])
} else {
matches!(
call_path.as_slice(),
[""] | ["asyncio", "TimeoutError"] | ["socket", "timeout"]
)
}
})
}
/// Return `true` if an [`Expr`] is `TimeoutError`.
fn is_timeout_error(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "TimeoutError"]))
}
/// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`].
fn atom_diagnostic(checker: &mut Checker, target: &Expr) {
let mut diagnostic = Diagnostic::new(
TimeoutErrorAlias {
name: compose_call_path(target),
},
target.range(),
);
if checker.semantic().is_builtin("TimeoutError") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
"TimeoutError".to_string(),
target.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// Create a [`Diagnostic`] for a tuple of expressions.
fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) {
let mut diagnostic = Diagnostic::new(TimeoutErrorAlias { name: None }, tuple.range());
if checker.semantic().is_builtin("TimeoutError") {
// Filter out any `TimeoutErrors` aliases.
let mut remaining: Vec<Expr> = tuple
.elts
.iter()
.filter_map(|elt| {
if aliases.contains(&elt) {
None
} else {
Some(elt.clone())
}
})
.collect();
// If `TimeoutError` itself isn't already in the tuple, add it.
if tuple
.elts
.iter()
.all(|elt| !is_timeout_error(elt, checker.semantic()))
{
let node = ast::ExprName {
id: "TimeoutError".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
remaining.insert(0, node.into());
}
let content = if remaining.len() == 1 {
"TimeoutError".to_string()
} else {
let node = ast::ExprTuple {
elts: remaining,
ctx: ExprContext::Load,
range: TextRange::default(),
};
format!("({})", checker.generator().expr(&node.into()))
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(content, tuple.range(), checker.locator()),
tuple.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// UP041
pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[ExceptHandler]) {
for handler in handlers {
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) = handler;
let Some(expr) = type_.as_ref() else {
continue;
};
match expr.as_ref() {
Expr::Name(_) | Expr::Attribute(_) => {
if is_alias(expr, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, expr);
}
}
Expr::Tuple(tuple) => {
// List of aliases to replace with `TimeoutError`.
let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts {
if is_alias(elt, checker.semantic(), checker.settings.target_version) {
aliases.push(elt);
}
}
if !aliases.is_empty() {
tuple_diagnostic(checker, tuple, &aliases);
}
}
_ => {}
}
}
}
/// UP041
pub(crate) fn timeout_error_alias_call(checker: &mut Checker, func: &Expr) {
if is_alias(func, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, func);
}
}
/// UP041
pub(crate) fn timeout_error_alias_raise(checker: &mut Checker, expr: &Expr) {
if matches!(expr, Expr::Name(_) | Expr::Attribute(_)) {
if is_alias(expr, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, expr);
}
}
}

View File

@@ -138,7 +138,7 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
stmt.range(),
);
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
// See https://github.com/astral-sh/ruff/issues/6434
let fix = if checker.source_type.is_stub() {
Fix::safe_edit(edit)

View File

@@ -353,7 +353,7 @@ UP032_0.py:37:1: UP032 [*] Use f-string instead of `format` call
35 35 | "foo{}".format(1)
36 36 |
37 |-r"foo{}".format(1)
37 |+rf"foo{1}"
37 |+fr"foo{1}"
38 38 |
39 39 | x = "{a}".format(a=1)
40 40 |

View File

@@ -1,104 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP041.py:5:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
3 | try:
4 | pass
5 | except asyncio.TimeoutError:
| ^^^^^^^^^^^^^^^^^^^^ UP041
6 | pass
|
= help: Replace `asyncio.TimeoutError` with builtin `TimeoutError`
Fix
2 2 | # These should be fixed
3 3 | try:
4 4 | pass
5 |-except asyncio.TimeoutError:
5 |+except TimeoutError:
6 6 | pass
7 7 |
8 8 | try:
UP041.py:17:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
15 | try:
16 | pass
17 | except (asyncio.TimeoutError,):
| ^^^^^^^^^^^^^^^^^^^^^^^ UP041
18 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
14 14 |
15 15 | try:
16 16 | pass
17 |-except (asyncio.TimeoutError,):
17 |+except TimeoutError:
18 18 | pass
19 19 |
20 20 | try:
UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
25 | try:
26 | pass
27 | except (asyncio.TimeoutError, socket.timeout,):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
28 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
24 24 |
25 25 | try:
26 26 | pass
27 |-except (asyncio.TimeoutError, socket.timeout,):
27 |+except (TimeoutError, socket.timeout):
28 28 | pass
29 29 |
30 30 | # Should be kept in parentheses (because multiple)
UP041.py:34:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
32 | try:
33 | pass
34 | except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
35 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
31 31 |
32 32 | try:
33 33 | pass
34 |-except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
34 |+except (socket.timeout, KeyError, TimeoutError):
35 35 | pass
36 36 |
37 37 | # First should change, second should not
UP041.py:42:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
40 | try:
41 | pass
42 | except (asyncio.TimeoutError, error):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
43 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
39 39 | from .mmap import error
40 40 | try:
41 41 | pass
42 |-except (asyncio.TimeoutError, error):
42 |+except (TimeoutError, error):
43 43 | pass
44 44 |
45 45 | # These should not change

View File

@@ -34,30 +34,3 @@ pub(super) fn generate_method_call(name: &str, method: &str, generator: Generato
};
generator.stmt(&stmt.into())
}
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
pub(super) fn generate_none_identity_comparison(
name: &str,
negate: bool,
generator: Generator,
) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name is None` or `name is not None`.
let op = if negate {
ast::CmpOp::IsNot
} else {
ast::CmpOp::Is
};
let compare = ast::ExprCompare {
left: Box::new(var.into()),
ops: vec![op],
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
range: TextRange::default(),
};
generator.expr(&compare.into())
}

View File

@@ -25,7 +25,6 @@ mod tests {
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))]
#[test_case(Rule::IsinstanceTypeNone, Path::new("FURB168.py"))]
#[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -97,9 +97,9 @@ pub(crate) fn check_and_remove_from_set(checker: &mut Checker, if_stmt: &ast::St
// Check if what we assume is set is indeed a set.
if !checker
.semantic()
.only_binding(check_set)
.map(|id| checker.semantic().binding(id))
.is_some_and(|binding| is_set(binding, checker.semantic()))
.resolve_name(check_set)
.map(|binding_id| checker.semantic().binding(binding_id))
.map_or(false, |binding| is_set(binding, checker.semantic()))
{
return;
};

View File

@@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::{is_dict, is_list};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -70,7 +70,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
// Fix is only supported for single-target deletions.
if delete.targets.len() == 1 {
let replacement = generate_method_call(&name.id, "clear", checker.generator());
let replacement = generate_method_call(name, "clear", checker.generator());
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
replacement,
delete.start(),
@@ -83,7 +83,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
}
/// Match `del expr[:]` where `expr` is a list or a dict.
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a ast::ExprName> {
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a str> {
// Check that it is `del expr[...]`.
let subscript = expr.as_subscript_expr()?;
@@ -100,9 +100,22 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a
return None;
}
// Check that it is del var[:]
let ast::ExprName { id: name, .. } = subscript.value.as_name_expr()?;
// Let's find definition for var
let scope = semantic.current_scope();
let bindings: Vec<&Binding> = scope
.get_all(name)
.map(|binding_id| semantic.binding(binding_id))
.collect();
// NOTE: Maybe it is too strict of a limitation, but it seems reasonable.
let [binding] = bindings.as_slice() else {
return None;
};
// It should only apply to variables that are known to be lists or dicts.
let name = subscript.value.as_name_expr()?;
let binding = semantic.binding(semantic.only_binding(name)?);
if !(is_dict(binding, semantic) || is_list(binding, semantic)) {
return None;
}

View File

@@ -2,11 +2,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_python_ast::{self as ast, Expr, Operator};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use ruff_python_codegen::Generator;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::rules::refurb::helpers::generate_none_identity_comparison;
/// ## What it does
/// Checks for uses of `isinstance` that check if an object is of type `None`.
@@ -69,8 +69,7 @@ pub(crate) fn isinstance_type_none(checker: &mut Checker, call: &ast::ExprCall)
return;
};
let mut diagnostic = Diagnostic::new(IsinstanceTypeNone, call.range());
let replacement =
generate_none_identity_comparison(object_name, false, checker.generator());
let replacement = generate_replacement(object_name, checker.generator());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(replacement, call.range(), checker.locator()),
call.range(),
@@ -118,3 +117,21 @@ fn is_none(expr: &Expr) -> bool {
}
inner(expr, false)
}
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
fn generate_replacement(name: &str, generator: Generator) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name is None`.
let compare = ast::ExprCompare {
left: Box::new(var.into()),
ops: vec![ast::CmpOp::Is],
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
range: TextRange::default(),
};
generator.expr(&compare.into())
}

View File

@@ -8,7 +8,6 @@ pub(crate) use reimplemented_starmap::*;
pub(crate) use repeated_append::*;
pub(crate) use single_item_membership_test::*;
pub(crate) use slice_copy::*;
pub(crate) use type_none_comparison::*;
pub(crate) use unnecessary_enumerate::*;
mod check_and_remove_from_set;
@@ -21,5 +20,4 @@ mod reimplemented_starmap;
mod repeated_append;
mod single_item_membership_test;
mod slice_copy;
mod type_none_comparison;
mod unnecessary_enumerate;

View File

@@ -76,7 +76,7 @@ impl Violation for RepeatedAppend {
/// FURB113
pub(crate) fn repeated_append(checker: &mut Checker, stmt: &Stmt) {
let Some(appends) = match_consecutive_appends(stmt, checker.semantic()) else {
let Some(appends) = match_consecutive_appends(checker.semantic(), stmt) else {
return;
};
@@ -163,8 +163,8 @@ impl Ranged for AppendGroup<'_> {
/// Match consecutive calls to `append` on list variables starting from the given statement.
fn match_consecutive_appends<'a>(
stmt: &'a Stmt,
semantic: &'a SemanticModel,
stmt: &'a Stmt,
) -> Option<Vec<Append<'a>>> {
// Match the current statement, to see if it's an append.
let append = match_append(semantic, stmt)?;

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