Compare commits
1 Commits
zb/cache-u
...
micha/cach
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19881f4cc3 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,7 +22,6 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
|||||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||||
|
|
||||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||||
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
|
|
||||||
|
|
||||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||||
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||||
|
|||||||
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -4,6 +4,5 @@
|
|||||||
# Enable off-by-default rules.
|
# Enable off-by-default rules.
|
||||||
[rules]
|
[rules]
|
||||||
possibly-unresolved-reference = "warn"
|
possibly-unresolved-reference = "warn"
|
||||||
possibly-missing-import = "warn"
|
|
||||||
unused-ignore-comment = "warn"
|
unused-ignore-comment = "warn"
|
||||||
division-by-zero = "warn"
|
division-by-zero = "warn"
|
||||||
|
|||||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
name: Create an issue if the daily fuzz surfaced any bugs
|
name: Create an issue if the daily fuzz surfaced any bugs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: fuzz
|
needs: fuzz
|
||||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
|
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
5
.github/workflows/mypy_primer.yaml
vendored
5
.github/workflows/mypy_primer.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "crates/ty*/**"
|
- "crates/ty*/**"
|
||||||
- "!crates/ty_ide/**"
|
|
||||||
- "!crates/ty_server/**"
|
|
||||||
- "!crates/ty_test/**"
|
|
||||||
- "!crates/ty_completion_eval/**"
|
|
||||||
- "!crates/ty_wasm/**"
|
|
||||||
- "crates/ruff_db"
|
- "crates/ruff_db"
|
||||||
- "crates/ruff_python_ast"
|
- "crates/ruff_python_ast"
|
||||||
- "crates/ruff_python_parser"
|
- "crates/ruff_python_parser"
|
||||||
|
|||||||
41
.github/workflows/sync_typeshed.yaml
vendored
41
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,7 +16,8 @@ name: Sync typeshed
|
|||||||
# 3. Once the Windows worker is done, a MacOS worker:
|
# 3. Once the Windows worker is done, a MacOS worker:
|
||||||
# a. Checks out the branch created by the Linux worker
|
# a. Checks out the branch created by the Linux worker
|
||||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||||
# c. Formats the code again
|
# c. Attempts to update any snapshots that might have changed
|
||||||
|
# (this sub-step is allowed to fail)
|
||||||
# d. Commits the changes and pushes them to the same upstream branch
|
# d. Commits the changes and pushes them to the same upstream branch
|
||||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||||
@@ -197,6 +198,42 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||||
git commit -am "Remove pyproject.toml file"
|
git commit -am "Remove pyproject.toml file"
|
||||||
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
|
- name: "Install Rust toolchain"
|
||||||
|
if: ${{ success() }}
|
||||||
|
run: rustup show
|
||||||
|
- name: "Install mold"
|
||||||
|
if: ${{ success() }}
|
||||||
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
|
- name: "Install cargo nextest"
|
||||||
|
if: ${{ success() }}
|
||||||
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
|
with:
|
||||||
|
tool: cargo-nextest
|
||||||
|
- name: "Install cargo insta"
|
||||||
|
if: ${{ success() }}
|
||||||
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
|
with:
|
||||||
|
tool: cargo-insta
|
||||||
|
- name: Update snapshots
|
||||||
|
if: ${{ success() }}
|
||||||
|
run: |
|
||||||
|
cargo r \
|
||||||
|
--profile=profiling \
|
||||||
|
-p ty_completion_eval \
|
||||||
|
-- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||||
|
|
||||||
|
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
|
||||||
|
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
|
||||||
|
#
|
||||||
|
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
|
||||||
|
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
|
||||||
|
# We don't care about other tests failing here, we just want snapshots updated where possible,
|
||||||
|
# so we use `|| true` here to ignore the exit code.
|
||||||
|
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
|
||||||
|
- name: Commit snapshot changes
|
||||||
|
if: ${{ success() }}
|
||||||
|
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
|
||||||
- name: Push changes upstream and create a PR
|
- name: Push changes upstream and create a PR
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
run: |
|
run: |
|
||||||
@@ -208,7 +245,7 @@ jobs:
|
|||||||
name: Create an issue if the typeshed sync failed
|
name: Create an issue if the typeshed sync failed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
||||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
|
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
24
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
24
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,6 +17,7 @@ env:
|
|||||||
RUSTUP_MAX_RETRIES: 10
|
RUSTUP_MAX_RETRIES: 10
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
REF_NAME: ${{ github.ref_name }}
|
REF_NAME: ${{ github.ref_name }}
|
||||||
|
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ty-ecosystem-analyzer:
|
ty-ecosystem-analyzer:
|
||||||
@@ -66,7 +67,7 @@ jobs:
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||||
|
|
||||||
ecosystem-analyzer \
|
ecosystem-analyzer \
|
||||||
--repository ruff \
|
--repository ruff \
|
||||||
@@ -111,13 +112,22 @@ jobs:
|
|||||||
|
|
||||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
- name: "Deploy to Cloudflare Pages"
|
||||||
# Make sure to update the bot if you rename the artifact.
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
- name: "Upload full report"
|
id: deploy
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||||
with:
|
with:
|
||||||
name: full-report
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
path: dist/
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||||
|
|
||||||
|
- name: "Append deployment URL"
|
||||||
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
|
env:
|
||||||
|
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||||
|
run: |
|
||||||
|
echo >> comment.md
|
||||||
|
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
|
||||||
|
|
||||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||||
# Make sure to update the bot if you rename the artifact.
|
# Make sure to update the bot if you rename the artifact.
|
||||||
|
|||||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||||
|
|
||||||
ecosystem-analyzer \
|
ecosystem-analyzer \
|
||||||
--verbose \
|
--verbose \
|
||||||
|
|||||||
5
.github/workflows/typing_conformance.yaml
vendored
5
.github/workflows/typing_conformance.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "crates/ty*/**"
|
- "crates/ty*/**"
|
||||||
- "!crates/ty_ide/**"
|
|
||||||
- "!crates/ty_server/**"
|
|
||||||
- "!crates/ty_test/**"
|
|
||||||
- "!crates/ty_completion_eval/**"
|
|
||||||
- "!crates/ty_wasm/**"
|
|
||||||
- "crates/ruff_db"
|
- "crates/ruff_db"
|
||||||
- "crates/ruff_python_ast"
|
- "crates/ruff_python_ast"
|
||||||
- "crates/ruff_python_parser"
|
- "crates/ruff_python_parser"
|
||||||
|
|||||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,54 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.14.10
|
|
||||||
|
|
||||||
Released on 2025-12-18.
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- [formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
|
|
||||||
- [formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
|
|
||||||
- \[`flake8-implicit-str-concat`\] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
|
|
||||||
- \[`flake8-use-pathlib`\] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
|
|
||||||
- \[`refurb`\] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- \[`pyupgrade`\] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))
|
|
||||||
|
|
||||||
### Rule changes
|
|
||||||
|
|
||||||
- \[`eradicate`\] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
|
|
||||||
- \[`flake8-pytest-style`\] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
|
|
||||||
- [syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
|
|
||||||
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
|
|
||||||
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
|
|
||||||
- \[`flake8-bandit`\] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))
|
|
||||||
|
|
||||||
### Contributors
|
|
||||||
|
|
||||||
- [@dylwil3](https://github.com/dylwil3)
|
|
||||||
- [@charliecloudberry](https://github.com/charliecloudberry)
|
|
||||||
- [@charliermarsh](https://github.com/charliermarsh)
|
|
||||||
- [@chirizxc](https://github.com/chirizxc)
|
|
||||||
- [@ntBre](https://github.com/ntBre)
|
|
||||||
- [@zanieb](https://github.com/zanieb)
|
|
||||||
- [@amyreese](https://github.com/amyreese)
|
|
||||||
- [@hauntsaninja](https://github.com/hauntsaninja)
|
|
||||||
- [@11happy](https://github.com/11happy)
|
|
||||||
- [@mahiro72](https://github.com/mahiro72)
|
|
||||||
- [@MichaReiser](https://github.com/MichaReiser)
|
|
||||||
- [@phongddo](https://github.com/phongddo)
|
|
||||||
- [@PeterJCLaw](https://github.com/PeterJCLaw)
|
|
||||||
|
|
||||||
## 0.14.9
|
## 0.14.9
|
||||||
|
|
||||||
Released on 2025-12-11.
|
Released on 2025-12-11.
|
||||||
|
|||||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -1004,6 +1004,27 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dir-test"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62c013fe825864f3e4593f36426c1fa7a74f5603f13ca8d1af7a990c1cd94a79"
|
||||||
|
dependencies = [
|
||||||
|
"dir-test-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dir-test-macros"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d42f54d7b4a6bc2400fe5b338e35d1a335787585375322f49c5d5fe7b243da7e"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -2887,7 +2908,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argfile",
|
"argfile",
|
||||||
@@ -3145,7 +3166,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -3504,7 +3525,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_wasm"
|
name = "ruff_wasm"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
"console_log",
|
||||||
@@ -4492,7 +4513,7 @@ dependencies = [
|
|||||||
"camino",
|
"camino",
|
||||||
"colored 3.0.0",
|
"colored 3.0.0",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"datatest-stable",
|
"dir-test",
|
||||||
"drop_bomb",
|
"drop_bomb",
|
||||||
"get-size2",
|
"get-size2",
|
||||||
"glob",
|
"glob",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ criterion = { version = "0.7.0", default-features = false }
|
|||||||
crossbeam = { version = "0.8.4" }
|
crossbeam = { version = "0.8.4" }
|
||||||
dashmap = { version = "6.0.1" }
|
dashmap = { version = "6.0.1" }
|
||||||
datatest-stable = { version = "0.3.3" }
|
datatest-stable = { version = "0.3.3" }
|
||||||
|
dir-test = { version = "0.4.0" }
|
||||||
dunce = { version = "1.0.5" }
|
dunce = { version = "1.0.5" }
|
||||||
drop_bomb = { version = "0.1.5" }
|
drop_bomb = { version = "0.1.5" }
|
||||||
etcetera = { version = "0.11.0" }
|
etcetera = { version = "0.11.0" }
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -57,11 +57,8 @@ Ruff is extremely actively developed and used in major open-source projects like
|
|||||||
|
|
||||||
...and [many more](#whos-using-ruff).
|
...and [many more](#whos-using-ruff).
|
||||||
|
|
||||||
Ruff is backed by [Astral](https://astral.sh), the creators of
|
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
||||||
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||||
|
|
||||||
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
|
||||||
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
|
||||||
|
|
||||||
## Testimonials
|
## Testimonials
|
||||||
|
|
||||||
@@ -150,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
|||||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||||
|
|
||||||
# For a specific version.
|
# For a specific version.
|
||||||
curl -LsSf https://astral.sh/ruff/0.14.10/install.sh | sh
|
curl -LsSf https://astral.sh/ruff/0.14.9/install.sh | sh
|
||||||
powershell -c "irm https://astral.sh/ruff/0.14.10/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/0.14.9/install.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||||
@@ -184,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
|||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.10
|
rev: v0.14.9
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ extend-exclude = [
|
|||||||
"crates/ty_vendored/vendor/**/*",
|
"crates/ty_vendored/vendor/**/*",
|
||||||
"**/resources/**/*",
|
"**/resources/**/*",
|
||||||
"**/snapshots/**/*",
|
"**/snapshots/**/*",
|
||||||
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
|
|
||||||
# Completion tests tend to have a lot of incomplete
|
# Completion tests tend to have a lot of incomplete
|
||||||
# words naturally. It's annoying to have to make all
|
# words naturally. It's annoying to have to make all
|
||||||
# of them actually words. So just ignore typos here.
|
# of them actually words. So just ignore typos here.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
publish = true
|
publish = true
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
|||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
13100,
|
13030,
|
||||||
);
|
);
|
||||||
|
|
||||||
static TANJUN: Benchmark = Benchmark::new(
|
static TANJUN: Benchmark = Benchmark::new(
|
||||||
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
|||||||
max_dep_date: "2025-08-09",
|
max_dep_date: "2025-08-09",
|
||||||
python_version: PythonVersion::PY311,
|
python_version: PythonVersion::PY311,
|
||||||
},
|
},
|
||||||
1100,
|
950,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use glob::PatternError;
|
use glob::PatternError;
|
||||||
use ruff_notebook::{Notebook, NotebookError};
|
use ruff_notebook::{Notebook, NotebookError};
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -21,44 +20,18 @@ use super::walk_directory::WalkDirectoryBuilder;
|
|||||||
///
|
///
|
||||||
/// ## Warning
|
/// ## Warning
|
||||||
/// Don't use this system for production code. It's intended for testing only.
|
/// Don't use this system for production code. It's intended for testing only.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TestSystem {
|
pub struct TestSystem {
|
||||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||||
/// Environment variable overrides. If a key is present here, it takes precedence
|
|
||||||
/// over the inner system's environment variables.
|
|
||||||
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for TestSystem {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
env_overrides: self.env_overrides.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestSystem {
|
impl TestSystem {
|
||||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(inner),
|
inner: Arc::new(inner),
|
||||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets an environment variable override. This takes precedence over the inner system.
|
|
||||||
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
|
||||||
self.env_overrides
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(name.into(), Some(value.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes an environment variable override, making it appear as not set.
|
|
||||||
pub fn remove_env_var(&self, name: impl Into<String>) {
|
|
||||||
self.env_overrides.lock().unwrap().insert(name.into(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the [`InMemorySystem`].
|
/// Returns the [`InMemorySystem`].
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
@@ -174,18 +147,6 @@ impl System for TestSystem {
|
|||||||
self.system().case_sensitivity()
|
self.system().case_sensitivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
|
||||||
// Check overrides first
|
|
||||||
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
|
||||||
return match override_value {
|
|
||||||
Some(value) => Ok(value.clone()),
|
|
||||||
None => Err(std::env::VarError::NotPresent),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Fall back to inner system
|
|
||||||
self.system().env_var(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dyn_clone(&self) -> Box<dyn System> {
|
fn dyn_clone(&self) -> Box<dyn System> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
@@ -195,7 +156,6 @@ impl Default for TestSystem {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(InMemorySystem::default()),
|
inner: Arc::new(InMemorySystem::default()),
|
||||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,12 @@ fn generate_markdown() -> String {
|
|||||||
let _ = writeln!(&mut output, "# Rules\n");
|
let _ = writeln!(&mut output, "# Rules\n");
|
||||||
|
|
||||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||||
lints.sort_by_key(|a| a.name());
|
lints.sort_by(|a, b| {
|
||||||
|
a.default_level()
|
||||||
|
.cmp(&b.default_level())
|
||||||
|
.reverse()
|
||||||
|
.then_with(|| a.name().cmp(&b.name()))
|
||||||
|
});
|
||||||
|
|
||||||
for lint in lints {
|
for lint in lints {
|
||||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||||
@@ -114,7 +119,7 @@ fn generate_markdown() -> String {
|
|||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
&mut output,
|
&mut output,
|
||||||
r#"<small>
|
r#"<small>
|
||||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||||
{status_text} ·
|
{status_text} ·
|
||||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
facts = (
|
|
||||||
"Lobsters have blue blood.",
|
|
||||||
"The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
)
|
|
||||||
|
|
||||||
facts = [
|
|
||||||
"Lobsters have blue blood.",
|
|
||||||
"The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
]
|
|
||||||
|
|
||||||
facts = {
|
|
||||||
"Lobsters have blue blood.",
|
|
||||||
"The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
}
|
|
||||||
|
|
||||||
facts = {
|
|
||||||
(
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
facts = (
|
|
||||||
"Octopuses have three hearts."
|
|
||||||
# Missing comma here.
|
|
||||||
"Honey never spoils.",
|
|
||||||
)
|
|
||||||
|
|
||||||
facts = [
|
|
||||||
"Octopuses have three hearts."
|
|
||||||
# Missing comma here.
|
|
||||||
"Honey never spoils.",
|
|
||||||
]
|
|
||||||
|
|
||||||
facts = {
|
|
||||||
"Octopuses have three hearts."
|
|
||||||
# Missing comma here.
|
|
||||||
"Honey never spoils.",
|
|
||||||
}
|
|
||||||
|
|
||||||
facts = (
|
|
||||||
(
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
facts = [
|
|
||||||
(
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
facts = (
|
|
||||||
"Lobsters have blue blood.\n"
|
|
||||||
"The liver is the only human organ that can fully regenerate itself.\n"
|
|
||||||
"Clarinets are made almost entirely out of wood from the mpingo tree.\n"
|
|
||||||
"In 1971, astronaut Alan Shepard played golf on the moon.\n"
|
|
||||||
)
|
|
||||||
@@ -9,15 +9,3 @@ def test_ok():
|
|||||||
def test_error():
|
def test_error():
|
||||||
with pytest.raises(UnicodeError):
|
with pytest.raises(UnicodeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_match_only():
|
|
||||||
with pytest.raises(match="some error message"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_check_only():
|
|
||||||
with pytest.raises(check=lambda e: True):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_match_and_check():
|
|
||||||
with pytest.raises(match="some error message", check=lambda e: True):
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -136,38 +136,4 @@ os.chmod("pth1_file", 0o700, None, True, 1, *[1], **{"x": 1}, foo=1)
|
|||||||
os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1)
|
os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||||
os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1)
|
os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||||
|
|
||||||
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||||
|
|
||||||
# See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
print("rename: truthy")
|
|
||||||
else:
|
|
||||||
print("rename: falsey")
|
|
||||||
|
|
||||||
if os.replace("pth1.py.bak", "pth1.py"):
|
|
||||||
print("replace: truthy")
|
|
||||||
else:
|
|
||||||
print("replace: falsey")
|
|
||||||
|
|
||||||
try:
|
|
||||||
for _ in os.getcwd():
|
|
||||||
print("getcwd: iterable")
|
|
||||||
break
|
|
||||||
except TypeError as e:
|
|
||||||
print("getcwd: not iterable")
|
|
||||||
|
|
||||||
try:
|
|
||||||
for _ in os.getcwdb():
|
|
||||||
print("getcwdb: iterable")
|
|
||||||
break
|
|
||||||
except TypeError as e:
|
|
||||||
print("getcwdb: not iterable")
|
|
||||||
|
|
||||||
try:
|
|
||||||
for _ in os.readlink(sys.executable):
|
|
||||||
print("readlink: iterable")
|
|
||||||
break
|
|
||||||
except TypeError as e:
|
|
||||||
print("readlink: not iterable")
|
|
||||||
@@ -132,6 +132,7 @@ async def c():
|
|||||||
# Non-errors
|
# Non-errors
|
||||||
###
|
###
|
||||||
|
|
||||||
|
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
||||||
"\N{snowman} {}".format(a)
|
"\N{snowman} {}".format(a)
|
||||||
|
|
||||||
"{".format(a)
|
"{".format(a)
|
||||||
@@ -275,6 +276,3 @@ if __name__ == "__main__":
|
|||||||
number = 0
|
number = 0
|
||||||
string = "{}".format(number := number + 1)
|
string = "{}".format(number := number + 1)
|
||||||
print(string)
|
print(string)
|
||||||
|
|
||||||
# Unicode escape
|
|
||||||
"\N{angle}AOB = {angle}°".format(angle=180)
|
|
||||||
|
|||||||
@@ -138,6 +138,5 @@ with open("file.txt", encoding="utf-8") as f:
|
|||||||
with open("file.txt", encoding="utf-8") as f:
|
with open("file.txt", encoding="utf-8") as f:
|
||||||
contents = process_contents(f.read())
|
contents = process_contents(f.read())
|
||||||
|
|
||||||
with open("file1.txt", encoding="utf-8") as f:
|
with open("file.txt", encoding="utf-8") as f:
|
||||||
contents: str = process_contents(f.read())
|
contents: str = process_contents(f.read())
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
with Path("file.txt").open() as f:
|
|
||||||
contents = f.read()
|
|
||||||
|
|
||||||
with Path("file.txt").open("r") as f:
|
|
||||||
contents = f.read()
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
with Path("file.txt").open("w") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
with Path("file.txt").open("wb") as f:
|
|
||||||
f.write(b"test")
|
|
||||||
|
|
||||||
with Path("file.txt").open(mode="w") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
with Path("file.txt").open("w", encoding="utf8") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
with Path("file.txt").open("w", errors="ignore") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
with Path(foo()).open("w") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
p = Path("file.txt")
|
|
||||||
with p.open("w") as f:
|
|
||||||
f.write("test")
|
|
||||||
|
|
||||||
with Path("foo", "bar", "baz").open("w") as f:
|
|
||||||
f.write("test")
|
|
||||||
@@ -86,26 +86,3 @@ def f():
|
|||||||
# Multiple codes but none are used
|
# Multiple codes but none are used
|
||||||
# ruff: disable[E741, F401, F841]
|
# ruff: disable[E741, F401, F841]
|
||||||
print("hello")
|
print("hello")
|
||||||
|
|
||||||
|
|
||||||
def f():
|
|
||||||
# Unknown rule codes
|
|
||||||
# ruff: disable[YF829]
|
|
||||||
# ruff: disable[F841, RQW320]
|
|
||||||
value = 0
|
|
||||||
# ruff: enable[F841, RQW320]
|
|
||||||
# ruff: enable[YF829]
|
|
||||||
|
|
||||||
|
|
||||||
def f():
|
|
||||||
# External rule codes should be ignored
|
|
||||||
# ruff: disable[TK421]
|
|
||||||
print("hello")
|
|
||||||
# ruff: enable[TK421]
|
|
||||||
|
|
||||||
|
|
||||||
def f():
|
|
||||||
# Empty or missing rule codes
|
|
||||||
# ruff: disable
|
|
||||||
# ruff: disable[]
|
|
||||||
print("hello")
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
a: int = 1
|
|
||||||
def f1():
|
|
||||||
global a
|
|
||||||
a: str = "foo" # error
|
|
||||||
|
|
||||||
b: int = 1
|
|
||||||
def outer():
|
|
||||||
def inner():
|
|
||||||
global b
|
|
||||||
b: str = "nested" # error
|
|
||||||
|
|
||||||
c: int = 1
|
|
||||||
def f2():
|
|
||||||
global c
|
|
||||||
c: list[str] = [] # error
|
|
||||||
|
|
||||||
d: int = 1
|
|
||||||
def f3():
|
|
||||||
global d
|
|
||||||
d: str # error
|
|
||||||
|
|
||||||
e: int = 1
|
|
||||||
def f4():
|
|
||||||
e: str = "happy" # okay
|
|
||||||
|
|
||||||
global f
|
|
||||||
f: int = 1 # okay
|
|
||||||
|
|
||||||
g: int = 1
|
|
||||||
global g # error
|
|
||||||
|
|
||||||
class C:
|
|
||||||
x: str
|
|
||||||
global x # error
|
|
||||||
|
|
||||||
class D:
|
|
||||||
global x # error
|
|
||||||
x: str
|
|
||||||
@@ -214,13 +214,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
}) => {
|
}) => {
|
||||||
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
|
||||||
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
|
||||||
checker,
|
|
||||||
expr,
|
|
||||||
elts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ctx.is_store() {
|
if ctx.is_store() {
|
||||||
let check_too_many_expressions =
|
let check_too_many_expressions =
|
||||||
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
||||||
@@ -1336,13 +1329,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Set(set) => {
|
Expr::Set(set) => {
|
||||||
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
|
||||||
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
|
||||||
checker,
|
|
||||||
expr,
|
|
||||||
&set.elts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if checker.is_rule_enabled(Rule::DuplicateValue) {
|
if checker.is_rule_enabled(Rule::DuplicateValue) {
|
||||||
flake8_bugbear::rules::duplicate_value(checker, set);
|
flake8_bugbear::rules::duplicate_value(checker, set);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,7 +454,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Flake8ImplicitStrConcat, "001") => rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "001") => rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation,
|
||||||
(Flake8ImplicitStrConcat, "002") => rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "002") => rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
|
||||||
(Flake8ImplicitStrConcat, "003") => rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "003") => rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
|
||||||
(Flake8ImplicitStrConcat, "004") => rules::flake8_implicit_str_concat::rules::ImplicitStringConcatenationInCollectionLiteral,
|
|
||||||
|
|
||||||
// flake8-print
|
// flake8-print
|
||||||
(Flake8Print, "1") => rules::flake8_print::rules::Print,
|
(Flake8Print, "1") => rules::flake8_print::rules::Print,
|
||||||
@@ -1064,8 +1063,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
||||||
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
||||||
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
||||||
(Ruff, "103") => rules::ruff::rules::InvalidSuppressionComment,
|
|
||||||
(Ruff, "104") => rules::ruff::rules::UnmatchedSuppressionComment,
|
|
||||||
|
|
||||||
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
||||||
#[cfg(any(feature = "test-rules", test))]
|
#[cfg(any(feature = "test-rules", test))]
|
||||||
|
|||||||
@@ -1001,7 +1001,6 @@ mod tests {
|
|||||||
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
||||||
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
||||||
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
||||||
#[test_case(Path::new("annotated_global.py"), PythonVersion::PY314)]
|
|
||||||
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
||||||
let snapshot = format!(
|
let snapshot = format!(
|
||||||
"semantic_syntax_error_{}_{}",
|
"semantic_syntax_error_{}_{}",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ static ALLOWLIST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
|||||||
# Case-sensitive
|
# Case-sensitive
|
||||||
pyright
|
pyright
|
||||||
| pyrefly
|
| pyrefly
|
||||||
| ruff\s*:\s*(disable|enable)
|
|
||||||
| mypy:
|
| mypy:
|
||||||
| type:\s*ignore
|
| type:\s*ignore
|
||||||
| SPDX-License-Identifier:
|
| SPDX-License-Identifier:
|
||||||
@@ -149,8 +148,6 @@ mod tests {
|
|||||||
assert!(!comment_contains_code("# 123", &[]));
|
assert!(!comment_contains_code("# 123", &[]));
|
||||||
assert!(!comment_contains_code("# 123.1", &[]));
|
assert!(!comment_contains_code("# 123.1", &[]));
|
||||||
assert!(!comment_contains_code("# 1, 2, 3", &[]));
|
assert!(!comment_contains_code("# 1, 2, 3", &[]));
|
||||||
assert!(!comment_contains_code("# ruff: disable[E501]", &[]));
|
|
||||||
assert!(!comment_contains_code("#ruff:enable[E501, F84]", &[]));
|
|
||||||
assert!(!comment_contains_code(
|
assert!(!comment_contains_code(
|
||||||
"# pylint: disable=redefined-outer-name",
|
"# pylint: disable=redefined-outer-name",
|
||||||
&[]
|
&[]
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
||||||
pub(crate) fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
|
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ mod async_zero_sleep;
|
|||||||
mod blocking_http_call;
|
mod blocking_http_call;
|
||||||
mod blocking_http_call_httpx;
|
mod blocking_http_call_httpx;
|
||||||
mod blocking_input;
|
mod blocking_input;
|
||||||
pub(crate) mod blocking_open_call;
|
mod blocking_open_call;
|
||||||
mod blocking_path_methods;
|
mod blocking_path_methods;
|
||||||
mod blocking_process_invocation;
|
mod blocking_process_invocation;
|
||||||
mod blocking_sleep;
|
mod blocking_sleep;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::{checkers::ast::Checker, settings::LinterSettings};
|
|||||||
/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup].
|
/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup].
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// [`markupsafe.Markup`][markupsafe-markup] does not perform any escaping, so passing dynamic
|
/// [`markupsafe.Markup`] does not perform any escaping, so passing dynamic
|
||||||
/// content, like f-strings, variables or interpolated strings will potentially
|
/// content, like f-strings, variables or interpolated strings will potentially
|
||||||
/// lead to XSS vulnerabilities.
|
/// lead to XSS vulnerabilities.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ mod tests {
|
|||||||
Path::new("ISC_syntax_error_2.py")
|
Path::new("ISC_syntax_error_2.py")
|
||||||
)]
|
)]
|
||||||
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"))]
|
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"))]
|
||||||
#[test_case(
|
|
||||||
Rule::ImplicitStringConcatenationInCollectionLiteral,
|
|
||||||
Path::new("ISC004.py")
|
|
||||||
)]
|
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
|
||||||
use ruff_python_ast::token::parenthesized_range;
|
|
||||||
use ruff_python_ast::{Expr, StringLike};
|
|
||||||
use ruff_text_size::Ranged;
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
|
||||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
|
||||||
|
|
||||||
/// ## What it does
|
|
||||||
/// Checks for implicitly concatenated strings inside list, tuple, and set literals.
|
|
||||||
///
|
|
||||||
/// ## Why is this bad?
|
|
||||||
/// In collection literals, implicit string concatenation is often the result of
|
|
||||||
/// a missing comma between elements, which can silently merge items together.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
/// ```python
|
|
||||||
/// facts = (
|
|
||||||
/// "Lobsters have blue blood.",
|
|
||||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
/// "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Instead, you likely intended:
|
|
||||||
/// ```python
|
|
||||||
/// facts = (
|
|
||||||
/// "Lobsters have blue blood.",
|
|
||||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree.",
|
|
||||||
/// "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// If the concatenation is intentional, wrap it in parentheses to make it
|
|
||||||
/// explicit:
|
|
||||||
/// ```python
|
|
||||||
/// facts = (
|
|
||||||
/// "Lobsters have blue blood.",
|
|
||||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
/// (
|
|
||||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
/// "In 1971, astronaut Alan Shepard played golf on the moon."
|
|
||||||
/// ),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Fix safety
|
|
||||||
/// The fix is safe in that it does not change the semantics of your code.
|
|
||||||
/// However, the issue is that you may often want to change semantics
|
|
||||||
/// by adding a missing comma.
|
|
||||||
#[derive(ViolationMetadata)]
|
|
||||||
#[violation_metadata(preview_since = "0.14.10")]
|
|
||||||
pub(crate) struct ImplicitStringConcatenationInCollectionLiteral;
|
|
||||||
|
|
||||||
impl Violation for ImplicitStringConcatenationInCollectionLiteral {
|
|
||||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always;
|
|
||||||
|
|
||||||
#[derive_message_formats]
|
|
||||||
fn message(&self) -> String {
|
|
||||||
"Unparenthesized implicit string concatenation in collection".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fix_title(&self) -> Option<String> {
|
|
||||||
Some("Wrap implicitly concatenated strings in parentheses".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISC004
|
|
||||||
pub(crate) fn implicit_string_concatenation_in_collection_literal(
|
|
||||||
checker: &Checker,
|
|
||||||
expr: &Expr,
|
|
||||||
elements: &[Expr],
|
|
||||||
) {
|
|
||||||
for element in elements {
|
|
||||||
let Ok(string_like) = StringLike::try_from(element) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if !string_like.is_implicit_concatenated() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if parenthesized_range(
|
|
||||||
string_like.as_expression_ref(),
|
|
||||||
expr.into(),
|
|
||||||
checker.tokens(),
|
|
||||||
)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut diagnostic = checker.report_diagnostic(
|
|
||||||
ImplicitStringConcatenationInCollectionLiteral,
|
|
||||||
string_like.range(),
|
|
||||||
);
|
|
||||||
diagnostic.help("Did you forget a comma?");
|
|
||||||
diagnostic.set_fix(Fix::unsafe_edits(
|
|
||||||
Edit::insertion("(".to_string(), string_like.range().start()),
|
|
||||||
[Edit::insertion(")".to_string(), string_like.range().end())],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
pub(crate) use collection_literal::*;
|
|
||||||
pub(crate) use explicit::*;
|
pub(crate) use explicit::*;
|
||||||
pub(crate) use implicit::*;
|
pub(crate) use implicit::*;
|
||||||
|
|
||||||
mod collection_literal;
|
|
||||||
mod explicit;
|
mod explicit;
|
||||||
mod implicit;
|
mod implicit;
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
|
||||||
---
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:4:5
|
|
||||||
|
|
|
||||||
2 | "Lobsters have blue blood.",
|
|
||||||
3 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
4 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
5 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
| |______________________________________________________________^
|
|
||||||
6 | )
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
1 | facts = (
|
|
||||||
2 | "Lobsters have blue blood.",
|
|
||||||
3 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
4 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
5 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
|
||||||
6 | )
|
|
||||||
7 |
|
|
||||||
8 | facts = [
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:11:5
|
|
||||||
|
|
|
||||||
9 | "Lobsters have blue blood.",
|
|
||||||
10 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
11 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
12 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
| |______________________________________________________________^
|
|
||||||
13 | ]
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
8 | facts = [
|
|
||||||
9 | "Lobsters have blue blood.",
|
|
||||||
10 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
11 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
12 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
|
||||||
13 | ]
|
|
||||||
14 |
|
|
||||||
15 | facts = {
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:18:5
|
|
||||||
|
|
|
||||||
16 | "Lobsters have blue blood.",
|
|
||||||
17 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
18 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
19 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
| |______________________________________________________________^
|
|
||||||
20 | }
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
15 | facts = {
|
|
||||||
16 | "Lobsters have blue blood.",
|
|
||||||
17 | "The liver is the only human organ that can fully regenerate itself.",
|
|
||||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
|
||||||
18 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
|
||||||
19 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
|
||||||
20 | }
|
|
||||||
21 |
|
|
||||||
22 | facts = {
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:30:5
|
|
||||||
|
|
|
||||||
29 | facts = (
|
|
||||||
30 | / "Octopuses have three hearts."
|
|
||||||
31 | | # Missing comma here.
|
|
||||||
32 | | "Honey never spoils.",
|
|
||||||
| |_________________________^
|
|
||||||
33 | )
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
27 | }
|
|
||||||
28 |
|
|
||||||
29 | facts = (
|
|
||||||
- "Octopuses have three hearts."
|
|
||||||
30 + ("Octopuses have three hearts."
|
|
||||||
31 | # Missing comma here.
|
|
||||||
- "Honey never spoils.",
|
|
||||||
32 + "Honey never spoils."),
|
|
||||||
33 | )
|
|
||||||
34 |
|
|
||||||
35 | facts = [
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:36:5
|
|
||||||
|
|
|
||||||
35 | facts = [
|
|
||||||
36 | / "Octopuses have three hearts."
|
|
||||||
37 | | # Missing comma here.
|
|
||||||
38 | | "Honey never spoils.",
|
|
||||||
| |_________________________^
|
|
||||||
39 | ]
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
33 | )
|
|
||||||
34 |
|
|
||||||
35 | facts = [
|
|
||||||
- "Octopuses have three hearts."
|
|
||||||
36 + ("Octopuses have three hearts."
|
|
||||||
37 | # Missing comma here.
|
|
||||||
- "Honey never spoils.",
|
|
||||||
38 + "Honey never spoils."),
|
|
||||||
39 | ]
|
|
||||||
40 |
|
|
||||||
41 | facts = {
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
|
||||||
--> ISC004.py:42:5
|
|
||||||
|
|
|
||||||
41 | facts = {
|
|
||||||
42 | / "Octopuses have three hearts."
|
|
||||||
43 | | # Missing comma here.
|
|
||||||
44 | | "Honey never spoils.",
|
|
||||||
| |_________________________^
|
|
||||||
45 | }
|
|
||||||
|
|
|
||||||
help: Wrap implicitly concatenated strings in parentheses
|
|
||||||
help: Did you forget a comma?
|
|
||||||
39 | ]
|
|
||||||
40 |
|
|
||||||
41 | facts = {
|
|
||||||
- "Octopuses have three hearts."
|
|
||||||
42 + ("Octopuses have three hearts."
|
|
||||||
43 | # Missing comma here.
|
|
||||||
- "Honey never spoils.",
|
|
||||||
44 + "Honey never spoils."),
|
|
||||||
45 | }
|
|
||||||
46 |
|
|
||||||
47 | facts = (
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
@@ -37,11 +37,10 @@ use crate::{Fix, FixAvailability, Violation};
|
|||||||
/// import logging
|
/// import logging
|
||||||
///
|
///
|
||||||
/// logging.basicConfig(level=logging.INFO)
|
/// logging.basicConfig(level=logging.INFO)
|
||||||
/// logger = logging.getLogger(__name__)
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// def sum_less_than_four(a, b):
|
/// def sum_less_than_four(a, b):
|
||||||
/// logger.debug("Calling sum_less_than_four")
|
/// logging.debug("Calling sum_less_than_four")
|
||||||
/// return a + b < 4
|
/// return a + b < 4
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -125,9 +125,6 @@ impl Violation for PytestRaisesTooBroad {
|
|||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// `pytest.raises` expects to receive an expected exception as its first
|
/// `pytest.raises` expects to receive an expected exception as its first
|
||||||
/// argument. If omitted, the `pytest.raises` call will fail at runtime.
|
/// argument. If omitted, the `pytest.raises` call will fail at runtime.
|
||||||
/// The rule will also accept calls without an expected exception but with
|
|
||||||
/// `match` and/or `check` keyword arguments, which are also valid after
|
|
||||||
/// pytest version 8.4.0.
|
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
@@ -184,8 +181,6 @@ pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) {
|
|||||||
.arguments
|
.arguments
|
||||||
.find_argument("expected_exception", 0)
|
.find_argument("expected_exception", 0)
|
||||||
.is_none()
|
.is_none()
|
||||||
&& call.arguments.find_keyword("match").is_none()
|
|
||||||
&& call.arguments.find_keyword("check").is_none()
|
|
||||||
{
|
{
|
||||||
checker.report_diagnostic(PytestRaisesWithoutException, call.func.range());
|
checker.report_diagnostic(PytestRaisesWithoutException, call.func.range());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ pub(crate) fn is_argument_non_default(arguments: &Arguments, name: &str, positio
|
|||||||
|
|
||||||
/// Returns `true` if the given call is a top-level expression in its statement.
|
/// Returns `true` if the given call is a top-level expression in its statement.
|
||||||
/// This means the call's return value is not used, so return type changes don't matter.
|
/// This means the call's return value is not used, so return type changes don't matter.
|
||||||
pub(crate) fn is_top_level_expression_in_statement(checker: &Checker) -> bool {
|
pub(crate) fn is_top_level_expression_call(checker: &Checker) -> bool {
|
||||||
checker.semantic().current_expression_parent().is_none()
|
checker.semantic().current_expression_parent().is_none()
|
||||||
&& checker.semantic().current_statement().is_expr_stmt()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use ruff_text_size::Ranged;
|
|||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::importer::ImportRequest;
|
use crate::importer::ImportRequest;
|
||||||
use crate::preview::is_fix_os_getcwd_enabled;
|
use crate::preview::is_fix_os_getcwd_enabled;
|
||||||
use crate::rules::flake8_use_pathlib::helpers::is_top_level_expression_in_statement;
|
use crate::rules::flake8_use_pathlib::helpers::is_top_level_expression_call;
|
||||||
use crate::{FixAvailability, Violation};
|
use crate::{FixAvailability, Violation};
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
@@ -89,7 +89,7 @@ pub(crate) fn os_getcwd(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
|||||||
|
|
||||||
// Unsafe when the fix would delete comments or change a used return value
|
// Unsafe when the fix would delete comments or change a used return value
|
||||||
let applicability = if checker.comment_ranges().intersects(range)
|
let applicability = if checker.comment_ranges().intersects(range)
|
||||||
|| !is_top_level_expression_in_statement(checker)
|
|| !is_top_level_expression_call(checker)
|
||||||
{
|
{
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
|
|||||||
use crate::preview::is_fix_os_readlink_enabled;
|
use crate::preview::is_fix_os_readlink_enabled;
|
||||||
use crate::rules::flake8_use_pathlib::helpers::{
|
use crate::rules::flake8_use_pathlib::helpers::{
|
||||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||||
is_top_level_expression_in_statement,
|
is_top_level_expression_call,
|
||||||
};
|
};
|
||||||
use crate::{FixAvailability, Violation};
|
use crate::{FixAvailability, Violation};
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str])
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let applicability = if !is_top_level_expression_in_statement(checker) {
|
let applicability = if !is_top_level_expression_call(checker) {
|
||||||
// Unsafe because the return type changes (str/bytes -> Path)
|
// Unsafe because the return type changes (str/bytes -> Path)
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
|
|||||||
use crate::preview::is_fix_os_rename_enabled;
|
use crate::preview::is_fix_os_rename_enabled;
|
||||||
use crate::rules::flake8_use_pathlib::helpers::{
|
use crate::rules::flake8_use_pathlib::helpers::{
|
||||||
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
|
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
|
||||||
is_keyword_only_argument_non_default, is_top_level_expression_in_statement,
|
is_keyword_only_argument_non_default, is_top_level_expression_call,
|
||||||
};
|
};
|
||||||
use crate::{FixAvailability, Violation};
|
use crate::{FixAvailability, Violation};
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ pub(crate) fn os_rename(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Unsafe when the fix would delete comments or change a used return value
|
// Unsafe when the fix would delete comments or change a used return value
|
||||||
let applicability = if !is_top_level_expression_in_statement(checker) {
|
let applicability = if !is_top_level_expression_call(checker) {
|
||||||
// Unsafe because the return type changes (None -> Path)
|
// Unsafe because the return type changes (None -> Path)
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
|
|||||||
use crate::preview::is_fix_os_replace_enabled;
|
use crate::preview::is_fix_os_replace_enabled;
|
||||||
use crate::rules::flake8_use_pathlib::helpers::{
|
use crate::rules::flake8_use_pathlib::helpers::{
|
||||||
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
|
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
|
||||||
is_keyword_only_argument_non_default, is_top_level_expression_in_statement,
|
is_keyword_only_argument_non_default, is_top_level_expression_call,
|
||||||
};
|
};
|
||||||
use crate::{FixAvailability, Violation};
|
use crate::{FixAvailability, Violation};
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ pub(crate) fn os_replace(checker: &Checker, call: &ExprCall, segments: &[&str])
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Unsafe when the fix would delete comments or change a used return value
|
// Unsafe when the fix would delete comments or change a used return value
|
||||||
let applicability = if !is_top_level_expression_in_statement(checker) {
|
let applicability = if !is_top_level_expression_call(checker) {
|
||||||
// Unsafe because the return type changes (None -> Path)
|
// Unsafe because the return type changes (None -> Path)
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -567,64 +567,5 @@ PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
|||||||
138 |
|
138 |
|
||||||
139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
|
|
|
|
||||||
help: Replace with `Path(...).samefile()`
|
help: Replace with `Path(...).samefile()`
|
||||||
|
|
||||||
PTH104 `os.rename()` should be replaced by `Path.rename()`
|
|
||||||
--> full_name.py:144:4
|
|
||||||
|
|
|
||||||
142 | import sys
|
|
||||||
143 |
|
|
||||||
144 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
| ^^^^^^^^^
|
|
||||||
145 | print("rename: truthy")
|
|
||||||
146 | else:
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).rename(...)`
|
|
||||||
|
|
||||||
PTH105 `os.replace()` should be replaced by `Path.replace()`
|
|
||||||
--> full_name.py:149:4
|
|
||||||
|
|
|
||||||
147 | print("rename: falsey")
|
|
||||||
148 |
|
|
||||||
149 | if os.replace("pth1.py.bak", "pth1.py"):
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
150 | print("replace: truthy")
|
|
||||||
151 | else:
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).replace(...)`
|
|
||||||
|
|
||||||
PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
|
||||||
--> full_name.py:155:14
|
|
||||||
|
|
|
||||||
154 | try:
|
|
||||||
155 | for _ in os.getcwd():
|
|
||||||
| ^^^^^^^^^
|
|
||||||
156 | print("getcwd: iterable")
|
|
||||||
157 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path.cwd()`
|
|
||||||
|
|
||||||
PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
|
||||||
--> full_name.py:162:14
|
|
||||||
|
|
|
||||||
161 | try:
|
|
||||||
162 | for _ in os.getcwdb():
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
163 | print("getcwdb: iterable")
|
|
||||||
164 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path.cwd()`
|
|
||||||
|
|
||||||
PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
|
||||||
--> full_name.py:169:14
|
|
||||||
|
|
|
||||||
168 | try:
|
|
||||||
169 | for _ in os.readlink(sys.executable):
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
170 | print("readlink: iterable")
|
|
||||||
171 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).readlink()`
|
|
||||||
|
|||||||
@@ -1037,142 +1037,5 @@ PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
|||||||
138 |
|
138 |
|
||||||
139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
|
|
|
|
||||||
help: Replace with `Path(...).samefile()`
|
help: Replace with `Path(...).samefile()`
|
||||||
|
|
||||||
PTH104 [*] `os.rename()` should be replaced by `Path.rename()`
|
|
||||||
--> full_name.py:144:4
|
|
||||||
|
|
|
||||||
142 | import sys
|
|
||||||
143 |
|
|
||||||
144 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
| ^^^^^^^^^
|
|
||||||
145 | print("rename: truthy")
|
|
||||||
146 | else:
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).rename(...)`
|
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
142 | import sys
|
|
||||||
143 + import pathlib
|
|
||||||
144 |
|
|
||||||
- if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
145 + if pathlib.Path("pth1.py").rename("pth1.py.bak"):
|
|
||||||
146 | print("rename: truthy")
|
|
||||||
147 | else:
|
|
||||||
148 | print("rename: falsey")
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
PTH105 [*] `os.replace()` should be replaced by `Path.replace()`
|
|
||||||
--> full_name.py:149:4
|
|
||||||
|
|
|
||||||
147 | print("rename: falsey")
|
|
||||||
148 |
|
|
||||||
149 | if os.replace("pth1.py.bak", "pth1.py"):
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
150 | print("replace: truthy")
|
|
||||||
151 | else:
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).replace(...)`
|
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
142 | import sys
|
|
||||||
143 + import pathlib
|
|
||||||
144 |
|
|
||||||
145 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
146 | print("rename: truthy")
|
|
||||||
147 | else:
|
|
||||||
148 | print("rename: falsey")
|
|
||||||
149 |
|
|
||||||
- if os.replace("pth1.py.bak", "pth1.py"):
|
|
||||||
150 + if pathlib.Path("pth1.py.bak").replace("pth1.py"):
|
|
||||||
151 | print("replace: truthy")
|
|
||||||
152 | else:
|
|
||||||
153 | print("replace: falsey")
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
|
|
||||||
--> full_name.py:155:14
|
|
||||||
|
|
|
||||||
154 | try:
|
|
||||||
155 | for _ in os.getcwd():
|
|
||||||
| ^^^^^^^^^
|
|
||||||
156 | print("getcwd: iterable")
|
|
||||||
157 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path.cwd()`
|
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
142 | import sys
|
|
||||||
143 + import pathlib
|
|
||||||
144 |
|
|
||||||
145 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
146 | print("rename: truthy")
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
153 | print("replace: falsey")
|
|
||||||
154 |
|
|
||||||
155 | try:
|
|
||||||
- for _ in os.getcwd():
|
|
||||||
156 + for _ in pathlib.Path.cwd():
|
|
||||||
157 | print("getcwd: iterable")
|
|
||||||
158 | break
|
|
||||||
159 | except TypeError as e:
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
|
|
||||||
--> full_name.py:162:14
|
|
||||||
|
|
|
||||||
161 | try:
|
|
||||||
162 | for _ in os.getcwdb():
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
163 | print("getcwdb: iterable")
|
|
||||||
164 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path.cwd()`
|
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
142 | import sys
|
|
||||||
143 + import pathlib
|
|
||||||
144 |
|
|
||||||
145 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
146 | print("rename: truthy")
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
160 | print("getcwd: not iterable")
|
|
||||||
161 |
|
|
||||||
162 | try:
|
|
||||||
- for _ in os.getcwdb():
|
|
||||||
163 + for _ in pathlib.Path.cwd():
|
|
||||||
164 | print("getcwdb: iterable")
|
|
||||||
165 | break
|
|
||||||
166 | except TypeError as e:
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
|
||||||
--> full_name.py:169:14
|
|
||||||
|
|
|
||||||
168 | try:
|
|
||||||
169 | for _ in os.readlink(sys.executable):
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
170 | print("readlink: iterable")
|
|
||||||
171 | break
|
|
||||||
|
|
|
||||||
help: Replace with `Path(...).readlink()`
|
|
||||||
140 |
|
|
||||||
141 | # See: https://github.com/astral-sh/ruff/issues/21794
|
|
||||||
142 | import sys
|
|
||||||
143 + import pathlib
|
|
||||||
144 |
|
|
||||||
145 | if os.rename("pth1.py", "pth1.py.bak"):
|
|
||||||
146 | print("rename: truthy")
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
167 | print("getcwdb: not iterable")
|
|
||||||
168 |
|
|
||||||
169 | try:
|
|
||||||
- for _ in os.readlink(sys.executable):
|
|
||||||
170 + for _ in pathlib.Path(sys.executable).readlink():
|
|
||||||
171 | print("readlink: iterable")
|
|
||||||
172 | break
|
|
||||||
173 | except TypeError as e:
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|||||||
@@ -902,76 +902,56 @@ help: Convert to f-string
|
|||||||
132 | # Non-errors
|
132 | # Non-errors
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:135:1
|
--> UP032_0.py:160:1
|
||||||
|
|
|
|
||||||
133 | ###
|
158 | r'"\N{snowman} {}".format(a)'
|
||||||
134 |
|
159 |
|
||||||
135 | "\N{snowman} {}".format(a)
|
160 | / "123456789 {}".format(
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
161 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
136 |
|
162 | | )
|
||||||
137 | "{".format(a)
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
132 | # Non-errors
|
|
||||||
133 | ###
|
|
||||||
134 |
|
|
||||||
- "\N{snowman} {}".format(a)
|
|
||||||
135 + f"\N{snowman} {a}"
|
|
||||||
136 |
|
|
||||||
137 | "{".format(a)
|
|
||||||
138 |
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:159:1
|
|
||||||
|
|
|
||||||
157 | r'"\N{snowman} {}".format(a)'
|
|
||||||
158 |
|
|
||||||
159 | / "123456789 {}".format(
|
|
||||||
160 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
|
||||||
161 | | )
|
|
||||||
| |_^
|
| |_^
|
||||||
162 |
|
163 |
|
||||||
163 | """
|
164 | """
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
156 |
|
157 |
|
||||||
157 | r'"\N{snowman} {}".format(a)'
|
158 | r'"\N{snowman} {}".format(a)'
|
||||||
158 |
|
159 |
|
||||||
- "123456789 {}".format(
|
- "123456789 {}".format(
|
||||||
- 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
- 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
- )
|
- )
|
||||||
159 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
160 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
||||||
160 |
|
161 |
|
||||||
161 | """
|
162 | """
|
||||||
162 | {}
|
163 | {}
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:163:1
|
--> UP032_0.py:164:1
|
||||||
|
|
|
|
||||||
161 | )
|
162 | )
|
||||||
162 |
|
163 |
|
||||||
163 | / """
|
164 | / """
|
||||||
164 | | {}
|
|
||||||
165 | | {}
|
165 | | {}
|
||||||
166 | | {}
|
166 | | {}
|
||||||
167 | | """.format(
|
167 | | {}
|
||||||
168 | | 1,
|
168 | | """.format(
|
||||||
169 | | 2,
|
169 | | 1,
|
||||||
170 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
170 | | 2,
|
||||||
171 | | )
|
171 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
|
172 | | )
|
||||||
| |_^
|
| |_^
|
||||||
172 |
|
173 |
|
||||||
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
160 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
161 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
161 | )
|
162 | )
|
||||||
162 |
|
163 |
|
||||||
163 + f"""
|
164 + f"""
|
||||||
164 + {1}
|
165 + {1}
|
||||||
165 + {2}
|
166 + {2}
|
||||||
166 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
167 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
||||||
167 | """
|
168 | """
|
||||||
- {}
|
- {}
|
||||||
- {}
|
- {}
|
||||||
- {}
|
- {}
|
||||||
@@ -980,408 +960,392 @@ help: Convert to f-string
|
|||||||
- 2,
|
- 2,
|
||||||
- 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
- 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
- )
|
- )
|
||||||
168 |
|
169 |
|
||||||
169 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
170 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
170 | """.format(
|
171 | """.format(
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:173:84
|
--> UP032_0.py:174:84
|
||||||
|
|
|
|
||||||
171 | )
|
172 | )
|
||||||
172 |
|
173 |
|
||||||
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
| ____________________________________________________________________________________^
|
| ____________________________________________________________________________________^
|
||||||
174 | | """.format(
|
175 | | """.format(
|
||||||
175 | | 111111
|
176 | | 111111
|
||||||
176 | | )
|
177 | | )
|
||||||
| |_^
|
| |_^
|
||||||
177 |
|
178 |
|
||||||
178 | "{}".format(
|
179 | "{}".format(
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
170 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
171 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
171 | )
|
172 | )
|
||||||
172 |
|
173 |
|
||||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
- """.format(
|
- """.format(
|
||||||
- 111111
|
- 111111
|
||||||
- )
|
- )
|
||||||
173 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
174 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
||||||
174 + """
|
175 + """
|
||||||
175 |
|
176 |
|
||||||
176 | "{}".format(
|
177 | "{}".format(
|
||||||
177 | [
|
178 | [
|
||||||
|
|
||||||
UP032 Use f-string instead of `format` call
|
UP032 Use f-string instead of `format` call
|
||||||
--> UP032_0.py:201:1
|
--> UP032_0.py:202:1
|
||||||
|
|
|
|
||||||
199 | "{}".format(**c)
|
200 | "{}".format(**c)
|
||||||
200 |
|
201 |
|
||||||
201 | / "{}".format(
|
202 | / "{}".format(
|
||||||
202 | | 1 # comment
|
203 | | 1 # comment
|
||||||
203 | | )
|
204 | | )
|
||||||
| |_^
|
| |_^
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:208:1
|
--> UP032_0.py:209:1
|
||||||
|
|
|
|
||||||
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||||
207 | # existing line length, so it's fine.
|
208 | # existing line length, so it's fine.
|
||||||
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
209 |
|
210 |
|
||||||
210 | # When fixing, trim the trailing empty string.
|
211 | # When fixing, trim the trailing empty string.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
205 |
|
206 |
|
||||||
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||||
207 | # existing line length, so it's fine.
|
208 | # existing line length, so it's fine.
|
||||||
- "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
- "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
208 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
209 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
||||||
209 |
|
210 |
|
||||||
210 | # When fixing, trim the trailing empty string.
|
211 | # When fixing, trim the trailing empty string.
|
||||||
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:211:18
|
--> UP032_0.py:212:18
|
||||||
|
|
|
|
||||||
210 | # When fixing, trim the trailing empty string.
|
211 | # When fixing, trim the trailing empty string.
|
||||||
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
| __________________^
|
| __________________^
|
||||||
212 | | "".format(new_dict, d))
|
213 | | "".format(new_dict, d))
|
||||||
| |_______________________________________^
|
| |_______________________________________^
|
||||||
213 |
|
214 |
|
||||||
214 | # When fixing, trim the trailing empty string.
|
215 | # When fixing, trim the trailing empty string.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
209 |
|
210 |
|
||||||
210 | # When fixing, trim the trailing empty string.
|
211 | # When fixing, trim the trailing empty string.
|
||||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
- "".format(new_dict, d))
|
- "".format(new_dict, d))
|
||||||
211 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
212 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
||||||
212 |
|
|
||||||
213 | # When fixing, trim the trailing empty string.
|
|
||||||
214 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:215:18
|
|
||||||
|
|
|
||||||
214 | # When fixing, trim the trailing empty string.
|
|
||||||
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
| __________________^
|
|
||||||
216 | | .format(new_dict, d))
|
|
||||||
| |_____________________________________^
|
|
||||||
217 |
|
|
||||||
218 | raise ValueError(
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
212 | "".format(new_dict, d))
|
|
||||||
213 |
|
213 |
|
||||||
214 | # When fixing, trim the trailing empty string.
|
214 | # When fixing, trim the trailing empty string.
|
||||||
|
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:216:18
|
||||||
|
|
|
||||||
|
215 | # When fixing, trim the trailing empty string.
|
||||||
|
216 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
| __________________^
|
||||||
|
217 | | .format(new_dict, d))
|
||||||
|
| |_____________________________________^
|
||||||
|
218 |
|
||||||
|
219 | raise ValueError(
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
|
213 | "".format(new_dict, d))
|
||||||
|
214 |
|
||||||
|
215 | # When fixing, trim the trailing empty string.
|
||||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
- .format(new_dict, d))
|
- .format(new_dict, d))
|
||||||
215 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
216 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
216 + )
|
217 + )
|
||||||
217 |
|
218 |
|
||||||
218 | raise ValueError(
|
219 | raise ValueError(
|
||||||
219 | "Conflicting configuration dicts: {!r} {!r}"
|
220 | "Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:219:5
|
--> UP032_0.py:220:5
|
||||||
|
|
|
|
||||||
218 | raise ValueError(
|
219 | raise ValueError(
|
||||||
219 | / "Conflicting configuration dicts: {!r} {!r}"
|
220 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||||
220 | | "".format(new_dict, d)
|
221 | | "".format(new_dict, d)
|
||||||
| |__________________________^
|
| |__________________________^
|
||||||
221 | )
|
222 | )
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
216 | .format(new_dict, d))
|
217 | .format(new_dict, d))
|
||||||
217 |
|
218 |
|
||||||
218 | raise ValueError(
|
219 | raise ValueError(
|
||||||
- "Conflicting configuration dicts: {!r} {!r}"
|
- "Conflicting configuration dicts: {!r} {!r}"
|
||||||
- "".format(new_dict, d)
|
- "".format(new_dict, d)
|
||||||
219 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
220 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
220 | )
|
|
||||||
221 |
|
|
||||||
222 | raise ValueError(
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:224:5
|
|
||||||
|
|
|
||||||
223 | raise ValueError(
|
|
||||||
224 | / "Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
225 | | "".format(new_dict, d)
|
|
||||||
| |__________________________^
|
|
||||||
226 |
|
|
||||||
227 | )
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
221 | )
|
221 | )
|
||||||
222 |
|
222 |
|
||||||
223 | raise ValueError(
|
223 | raise ValueError(
|
||||||
- "Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
- "".format(new_dict, d)
|
|
||||||
224 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
|
||||||
225 |
|
|
||||||
226 | )
|
|
||||||
227 |
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:230:1
|
--> UP032_0.py:225:5
|
||||||
|
|
|
|
||||||
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
224 | raise ValueError(
|
||||||
230 | / (
|
225 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||||
231 | | "{}"
|
226 | | "".format(new_dict, d)
|
||||||
232 | | "{{}}"
|
| |__________________________^
|
||||||
233 | | ).format(a)
|
227 |
|
||||||
| |___________^
|
228 | )
|
||||||
234 |
|
|
||||||
235 | ("{}" "{{}}").format(a)
|
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
|
222 | )
|
||||||
|
223 |
|
||||||
|
224 | raise ValueError(
|
||||||
|
- "Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
- "".format(new_dict, d)
|
||||||
|
225 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
|
226 |
|
||||||
|
227 | )
|
||||||
228 |
|
228 |
|
||||||
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
|
||||||
230 | (
|
|
||||||
231 + f"{a}"
|
|
||||||
232 | "{}"
|
|
||||||
- "{{}}"
|
|
||||||
- ).format(a)
|
|
||||||
233 + )
|
|
||||||
234 |
|
|
||||||
235 | ("{}" "{{}}").format(a)
|
|
||||||
236 |
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:235:1
|
--> UP032_0.py:231:1
|
||||||
|
|
|
|
||||||
233 | ).format(a)
|
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||||
234 |
|
231 | / (
|
||||||
235 | ("{}" "{{}}").format(a)
|
232 | | "{}"
|
||||||
|
233 | | "{{}}"
|
||||||
|
234 | | ).format(a)
|
||||||
|
| |___________^
|
||||||
|
235 |
|
||||||
|
236 | ("{}" "{{}}").format(a)
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
|
229 |
|
||||||
|
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||||
|
231 | (
|
||||||
|
232 + f"{a}"
|
||||||
|
233 | "{}"
|
||||||
|
- "{{}}"
|
||||||
|
- ).format(a)
|
||||||
|
234 + )
|
||||||
|
235 |
|
||||||
|
236 | ("{}" "{{}}").format(a)
|
||||||
|
237 |
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:236:1
|
||||||
|
|
|
||||||
|
234 | ).format(a)
|
||||||
|
235 |
|
||||||
|
236 | ("{}" "{{}}").format(a)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
232 | "{{}}"
|
233 | "{{}}"
|
||||||
233 | ).format(a)
|
234 | ).format(a)
|
||||||
234 |
|
235 |
|
||||||
- ("{}" "{{}}").format(a)
|
- ("{}" "{{}}").format(a)
|
||||||
235 + (f"{a}" "{}")
|
236 + (f"{a}" "{}")
|
||||||
236 |
|
|
||||||
237 |
|
237 |
|
||||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
238 |
|
||||||
|
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:239:1
|
--> UP032_0.py:240:1
|
||||||
|
|
|
|
||||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
239 | / (
|
240 | / (
|
||||||
240 | | "{}"
|
241 | | "{}"
|
||||||
241 | | "{{{}}}"
|
242 | | "{{{}}}"
|
||||||
242 | | ).format(a, b)
|
243 | | ).format(a, b)
|
||||||
| |______________^
|
| |______________^
|
||||||
243 |
|
244 |
|
||||||
244 | ("{}" "{{{}}}").format(a, b)
|
245 | ("{}" "{{{}}}").format(a, b)
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
237 |
|
238 |
|
||||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
239 | (
|
240 | (
|
||||||
- "{}"
|
- "{}"
|
||||||
- "{{{}}}"
|
- "{{{}}}"
|
||||||
- ).format(a, b)
|
- ).format(a, b)
|
||||||
240 + f"{a}"
|
241 + f"{a}"
|
||||||
241 + f"{{{b}}}"
|
242 + f"{{{b}}}"
|
||||||
242 + )
|
243 + )
|
||||||
243 |
|
244 |
|
||||||
244 | ("{}" "{{{}}}").format(a, b)
|
245 | ("{}" "{{{}}}").format(a, b)
|
||||||
245 |
|
246 |
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:244:1
|
--> UP032_0.py:245:1
|
||||||
|
|
|
|
||||||
242 | ).format(a, b)
|
243 | ).format(a, b)
|
||||||
243 |
|
244 |
|
||||||
244 | ("{}" "{{{}}}").format(a, b)
|
245 | ("{}" "{{{}}}").format(a, b)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
245 |
|
246 |
|
||||||
246 | # The dictionary should be parenthesized.
|
247 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
241 | "{{{}}}"
|
242 | "{{{}}}"
|
||||||
242 | ).format(a, b)
|
243 | ).format(a, b)
|
||||||
243 |
|
244 |
|
||||||
- ("{}" "{{{}}}").format(a, b)
|
- ("{}" "{{{}}}").format(a, b)
|
||||||
244 + (f"{a}" f"{{{b}}}")
|
245 + (f"{a}" f"{{{b}}}")
|
||||||
245 |
|
246 |
|
||||||
246 | # The dictionary should be parenthesized.
|
247 | # The dictionary should be parenthesized.
|
||||||
247 | "{}".format({0: 1}[0])
|
248 | "{}".format({0: 1}[0])
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:247:1
|
--> UP032_0.py:248:1
|
||||||
|
|
|
|
||||||
246 | # The dictionary should be parenthesized.
|
247 | # The dictionary should be parenthesized.
|
||||||
247 | "{}".format({0: 1}[0])
|
248 | "{}".format({0: 1}[0])
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
248 |
|
249 |
|
||||||
249 | # The dictionary should be parenthesized.
|
250 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
244 | ("{}" "{{{}}}").format(a, b)
|
245 | ("{}" "{{{}}}").format(a, b)
|
||||||
245 |
|
246 |
|
||||||
246 | # The dictionary should be parenthesized.
|
247 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}[0])
|
- "{}".format({0: 1}[0])
|
||||||
247 + f"{({0: 1}[0])}"
|
248 + f"{({0: 1}[0])}"
|
||||||
248 |
|
249 |
|
||||||
249 | # The dictionary should be parenthesized.
|
250 | # The dictionary should be parenthesized.
|
||||||
250 | "{}".format({0: 1}.bar)
|
251 | "{}".format({0: 1}.bar)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:250:1
|
--> UP032_0.py:251:1
|
||||||
|
|
|
|
||||||
249 | # The dictionary should be parenthesized.
|
250 | # The dictionary should be parenthesized.
|
||||||
250 | "{}".format({0: 1}.bar)
|
251 | "{}".format({0: 1}.bar)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
251 |
|
252 |
|
||||||
252 | # The dictionary should be parenthesized.
|
253 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
247 | "{}".format({0: 1}[0])
|
248 | "{}".format({0: 1}[0])
|
||||||
248 |
|
249 |
|
||||||
249 | # The dictionary should be parenthesized.
|
250 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}.bar)
|
- "{}".format({0: 1}.bar)
|
||||||
250 + f"{({0: 1}.bar)}"
|
251 + f"{({0: 1}.bar)}"
|
||||||
251 |
|
252 |
|
||||||
252 | # The dictionary should be parenthesized.
|
253 | # The dictionary should be parenthesized.
|
||||||
253 | "{}".format({0: 1}())
|
254 | "{}".format({0: 1}())
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:253:1
|
--> UP032_0.py:254:1
|
||||||
|
|
|
|
||||||
252 | # The dictionary should be parenthesized.
|
253 | # The dictionary should be parenthesized.
|
||||||
253 | "{}".format({0: 1}())
|
254 | "{}".format({0: 1}())
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
254 |
|
255 |
|
||||||
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
250 | "{}".format({0: 1}.bar)
|
251 | "{}".format({0: 1}.bar)
|
||||||
251 |
|
252 |
|
||||||
252 | # The dictionary should be parenthesized.
|
253 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}())
|
- "{}".format({0: 1}())
|
||||||
253 + f"{({0: 1}())}"
|
254 + f"{({0: 1}())}"
|
||||||
254 |
|
255 |
|
||||||
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||||
256 | "{x} {x}".format(x=foo())
|
257 | "{x} {x}".format(x=foo())
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:260:1
|
--> UP032_0.py:261:1
|
||||||
|
|
|
|
||||||
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||||
260 | "{0} {1}".format(foo(), foo())
|
261 | "{0} {1}".format(foo(), foo())
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
261 |
|
262 |
|
||||||
262 | # The call should be removed, but the string itself should remain.
|
263 | # The call should be removed, but the string itself should remain.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
257 | "{0} {0}".format(foo())
|
258 | "{0} {0}".format(foo())
|
||||||
258 |
|
259 |
|
||||||
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||||
- "{0} {1}".format(foo(), foo())
|
- "{0} {1}".format(foo(), foo())
|
||||||
260 + f"{foo()} {foo()}"
|
261 + f"{foo()} {foo()}"
|
||||||
261 |
|
262 |
|
||||||
262 | # The call should be removed, but the string itself should remain.
|
263 | # The call should be removed, but the string itself should remain.
|
||||||
263 | ''.format(self.project)
|
264 | ''.format(self.project)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:263:1
|
--> UP032_0.py:264:1
|
||||||
|
|
|
|
||||||
262 | # The call should be removed, but the string itself should remain.
|
263 | # The call should be removed, but the string itself should remain.
|
||||||
263 | ''.format(self.project)
|
264 | ''.format(self.project)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
264 |
|
265 |
|
||||||
265 | # The call should be removed, but the string itself should remain.
|
266 | # The call should be removed, but the string itself should remain.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
260 | "{0} {1}".format(foo(), foo())
|
261 | "{0} {1}".format(foo(), foo())
|
||||||
261 |
|
262 |
|
||||||
262 | # The call should be removed, but the string itself should remain.
|
263 | # The call should be removed, but the string itself should remain.
|
||||||
- ''.format(self.project)
|
- ''.format(self.project)
|
||||||
263 + ''
|
264 + ''
|
||||||
264 |
|
265 |
|
||||||
265 | # The call should be removed, but the string itself should remain.
|
266 | # The call should be removed, but the string itself should remain.
|
||||||
266 | "".format(self.project)
|
267 | "".format(self.project)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:266:1
|
--> UP032_0.py:267:1
|
||||||
|
|
|
|
||||||
265 | # The call should be removed, but the string itself should remain.
|
266 | # The call should be removed, but the string itself should remain.
|
||||||
266 | "".format(self.project)
|
267 | "".format(self.project)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
267 |
|
268 |
|
||||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
263 | ''.format(self.project)
|
264 | ''.format(self.project)
|
||||||
264 |
|
265 |
|
||||||
265 | # The call should be removed, but the string itself should remain.
|
266 | # The call should be removed, but the string itself should remain.
|
||||||
- "".format(self.project)
|
- "".format(self.project)
|
||||||
266 + ""
|
267 + ""
|
||||||
267 |
|
268 |
|
||||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:270:5
|
--> UP032_0.py:271:5
|
||||||
|
|
|
|
||||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
270 | x: "'{} + {}'.format(x, y)"
|
271 | x: "'{} + {}'.format(x, y)"
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
271 |
|
272 |
|
||||||
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
267 |
|
268 |
|
||||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
- x: "'{} + {}'.format(x, y)"
|
- x: "'{} + {}'.format(x, y)"
|
||||||
270 + x: "f'{x} + {y}'"
|
271 + x: "f'{x} + {y}'"
|
||||||
271 |
|
272 |
|
||||||
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||||
273 | # Fix should parenthesize walrus
|
274 | # Fix should parenthesize walrus
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:276:14
|
--> UP032_0.py:277:14
|
||||||
|
|
|
|
||||||
274 | if __name__ == "__main__":
|
275 | if __name__ == "__main__":
|
||||||
275 | number = 0
|
276 | number = 0
|
||||||
276 | string = "{}".format(number := number + 1)
|
277 | string = "{}".format(number := number + 1)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
277 | print(string)
|
278 | print(string)
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
273 | # Fix should parenthesize walrus
|
274 | # Fix should parenthesize walrus
|
||||||
274 | if __name__ == "__main__":
|
275 | if __name__ == "__main__":
|
||||||
275 | number = 0
|
276 | number = 0
|
||||||
- string = "{}".format(number := number + 1)
|
- string = "{}".format(number := number + 1)
|
||||||
276 + string = f"{(number := number + 1)}"
|
277 + string = f"{(number := number + 1)}"
|
||||||
277 | print(string)
|
278 | print(string)
|
||||||
278 |
|
|
||||||
279 | # Unicode escape
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:280:1
|
|
||||||
|
|
|
||||||
279 | # Unicode escape
|
|
||||||
280 | "\N{angle}AOB = {angle}°".format(angle=180)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
277 | print(string)
|
|
||||||
278 |
|
|
||||||
279 | # Unicode escape
|
|
||||||
- "\N{angle}AOB = {angle}°".format(angle=180)
|
|
||||||
280 + f"\N{angle}AOB = {180}°"
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ use std::borrow::Cow;
|
|||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
use ruff_python_ast::{self as ast, Expr, name::Name, token::parenthesized_range};
|
use ruff_python_ast::{self as ast, Expr, name::Name, token::parenthesized_range};
|
||||||
use ruff_python_codegen::Generator;
|
use ruff_python_codegen::Generator;
|
||||||
use ruff_python_semantic::{ResolvedReference, SemanticModel};
|
use ruff_python_semantic::{BindingId, ResolvedReference, SemanticModel};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::rules::flake8_async::rules::blocking_open_call::is_open_call_from_pathlib;
|
|
||||||
use crate::{Applicability, Edit, Fix};
|
use crate::{Applicability, Edit, Fix};
|
||||||
|
|
||||||
/// Format a code snippet to call `name.method()`.
|
/// Format a code snippet to call `name.method()`.
|
||||||
@@ -120,13 +119,14 @@ impl OpenMode {
|
|||||||
pub(super) struct FileOpen<'a> {
|
pub(super) struct FileOpen<'a> {
|
||||||
/// With item where the open happens, we use it for the reporting range.
|
/// With item where the open happens, we use it for the reporting range.
|
||||||
pub(super) item: &'a ast::WithItem,
|
pub(super) item: &'a ast::WithItem,
|
||||||
|
/// Filename expression used as the first argument in `open`, we use it in the diagnostic message.
|
||||||
|
pub(super) filename: &'a Expr,
|
||||||
/// The file open mode.
|
/// The file open mode.
|
||||||
pub(super) mode: OpenMode,
|
pub(super) mode: OpenMode,
|
||||||
/// The file open keywords.
|
/// The file open keywords.
|
||||||
pub(super) keywords: Vec<&'a ast::Keyword>,
|
pub(super) keywords: Vec<&'a ast::Keyword>,
|
||||||
/// We only check `open` operations whose file handles are used exactly once.
|
/// We only check `open` operations whose file handles are used exactly once.
|
||||||
pub(super) reference: &'a ResolvedReference,
|
pub(super) reference: &'a ResolvedReference,
|
||||||
pub(super) argument: OpenArgument<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileOpen<'_> {
|
impl FileOpen<'_> {
|
||||||
@@ -137,45 +137,6 @@ impl FileOpen<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub(super) enum OpenArgument<'a> {
|
|
||||||
/// The filename argument to `open`, e.g. "foo.txt" in:
|
|
||||||
///
|
|
||||||
/// ```py
|
|
||||||
/// f = open("foo.txt")
|
|
||||||
/// ```
|
|
||||||
Builtin { filename: &'a Expr },
|
|
||||||
/// The `Path` receiver of a `pathlib.Path.open` call, e.g. the `p` in the
|
|
||||||
/// context manager in:
|
|
||||||
///
|
|
||||||
/// ```py
|
|
||||||
/// p = Path("foo.txt")
|
|
||||||
/// with p.open() as f: ...
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// or `Path("foo.txt")` in
|
|
||||||
///
|
|
||||||
/// ```py
|
|
||||||
/// with Path("foo.txt").open() as f: ...
|
|
||||||
/// ```
|
|
||||||
Pathlib { path: &'a Expr },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenArgument<'_> {
|
|
||||||
pub(super) fn display<'src>(&self, source: &'src str) -> &'src str {
|
|
||||||
&source[self.range()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ranged for OpenArgument<'_> {
|
|
||||||
fn range(&self) -> TextRange {
|
|
||||||
match self {
|
|
||||||
OpenArgument::Builtin { filename } => filename.range(),
|
|
||||||
OpenArgument::Pathlib { path } => path.range(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find and return all `open` operations in the given `with` statement.
|
/// Find and return all `open` operations in the given `with` statement.
|
||||||
pub(super) fn find_file_opens<'a>(
|
pub(super) fn find_file_opens<'a>(
|
||||||
with: &'a ast::StmtWith,
|
with: &'a ast::StmtWith,
|
||||||
@@ -185,65 +146,10 @@ pub(super) fn find_file_opens<'a>(
|
|||||||
) -> Vec<FileOpen<'a>> {
|
) -> Vec<FileOpen<'a>> {
|
||||||
with.items
|
with.items
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| {
|
.filter_map(|item| find_file_open(item, with, semantic, read_mode, python_version))
|
||||||
find_file_open(item, with, semantic, read_mode, python_version)
|
|
||||||
.or_else(|| find_path_open(item, with, semantic, read_mode, python_version))
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_file_open<'a>(
|
|
||||||
item: &'a ast::WithItem,
|
|
||||||
with: &'a ast::StmtWith,
|
|
||||||
semantic: &'a SemanticModel<'a>,
|
|
||||||
read_mode: bool,
|
|
||||||
mode: OpenMode,
|
|
||||||
keywords: Vec<&'a ast::Keyword>,
|
|
||||||
argument: OpenArgument<'a>,
|
|
||||||
) -> Option<FileOpen<'a>> {
|
|
||||||
match mode {
|
|
||||||
OpenMode::ReadText | OpenMode::ReadBytes => {
|
|
||||||
if !read_mode {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpenMode::WriteText | OpenMode::WriteBytes => {
|
|
||||||
if read_mode {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let var = item.optional_vars.as_deref()?.as_name_expr()?;
|
|
||||||
let scope = semantic.current_scope();
|
|
||||||
|
|
||||||
let binding = scope.get_all(var.id.as_str()).find_map(|id| {
|
|
||||||
let b = semantic.binding(id);
|
|
||||||
(b.range() == var.range()).then_some(b)
|
|
||||||
})?;
|
|
||||||
let references: Vec<&ResolvedReference> = binding
|
|
||||||
.references
|
|
||||||
.iter()
|
|
||||||
.map(|id| semantic.reference(*id))
|
|
||||||
.filter(|reference| with.range().contains_range(reference.range()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let [reference] = references.as_slice() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(FileOpen {
|
|
||||||
item,
|
|
||||||
mode,
|
|
||||||
keywords,
|
|
||||||
reference,
|
|
||||||
argument,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find `open` operation in the given `with` item.
|
/// Find `open` operation in the given `with` item.
|
||||||
fn find_file_open<'a>(
|
fn find_file_open<'a>(
|
||||||
item: &'a ast::WithItem,
|
item: &'a ast::WithItem,
|
||||||
@@ -259,6 +165,8 @@ fn find_file_open<'a>(
|
|||||||
..
|
..
|
||||||
} = item.context_expr.as_call_expr()?;
|
} = item.context_expr.as_call_expr()?;
|
||||||
|
|
||||||
|
let var = item.optional_vars.as_deref()?.as_name_expr()?;
|
||||||
|
|
||||||
// Ignore calls with `*args` and `**kwargs`. In the exact case of `open(*filename, mode="w")`,
|
// Ignore calls with `*args` and `**kwargs`. In the exact case of `open(*filename, mode="w")`,
|
||||||
// it could be a match; but in all other cases, the call _could_ contain unsupported keyword
|
// it could be a match; but in all other cases, the call _could_ contain unsupported keyword
|
||||||
// arguments, like `buffering`.
|
// arguments, like `buffering`.
|
||||||
@@ -279,57 +187,58 @@ fn find_file_open<'a>(
|
|||||||
let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?;
|
let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?;
|
||||||
|
|
||||||
let mode = kw_mode.unwrap_or(pos_mode);
|
let mode = kw_mode.unwrap_or(pos_mode);
|
||||||
resolve_file_open(
|
|
||||||
item,
|
|
||||||
with,
|
|
||||||
semantic,
|
|
||||||
read_mode,
|
|
||||||
mode,
|
|
||||||
keywords,
|
|
||||||
OpenArgument::Builtin { filename },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_path_open<'a>(
|
match mode {
|
||||||
item: &'a ast::WithItem,
|
OpenMode::ReadText | OpenMode::ReadBytes => {
|
||||||
with: &'a ast::StmtWith,
|
if !read_mode {
|
||||||
semantic: &'a SemanticModel<'a>,
|
return None;
|
||||||
read_mode: bool,
|
}
|
||||||
python_version: PythonVersion,
|
}
|
||||||
) -> Option<FileOpen<'a>> {
|
OpenMode::WriteText | OpenMode::WriteBytes => {
|
||||||
let ast::ExprCall {
|
if read_mode {
|
||||||
func,
|
return None;
|
||||||
arguments: ast::Arguments { args, keywords, .. },
|
}
|
||||||
..
|
}
|
||||||
} = item.context_expr.as_call_expr()?;
|
}
|
||||||
if args.iter().any(Expr::is_starred_expr)
|
|
||||||
|| keywords.iter().any(|keyword| keyword.arg.is_none())
|
// Path.read_bytes and Path.write_bytes do not support any kwargs.
|
||||||
{
|
if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !is_open_call_from_pathlib(func, semantic) {
|
|
||||||
|
// Now we need to find what is this variable bound to...
|
||||||
|
let scope = semantic.current_scope();
|
||||||
|
let bindings: Vec<BindingId> = scope.get_all(var.id.as_str()).collect();
|
||||||
|
|
||||||
|
let binding = bindings
|
||||||
|
.iter()
|
||||||
|
.map(|id| semantic.binding(*id))
|
||||||
|
// We might have many bindings with the same name, but we only care
|
||||||
|
// for the one we are looking at right now.
|
||||||
|
.find(|binding| binding.range() == var.range())?;
|
||||||
|
|
||||||
|
// Since many references can share the same binding, we can limit our attention span
|
||||||
|
// exclusively to the body of the current `with` statement.
|
||||||
|
let references: Vec<&ResolvedReference> = binding
|
||||||
|
.references
|
||||||
|
.iter()
|
||||||
|
.map(|id| semantic.reference(*id))
|
||||||
|
.filter(|reference| with.range().contains_range(reference.range()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// And even with all these restrictions, if the file handle gets used not exactly once,
|
||||||
|
// it doesn't fit the bill.
|
||||||
|
let [reference] = references.as_slice() else {
|
||||||
return None;
|
return None;
|
||||||
}
|
|
||||||
let attr = func.as_attribute_expr()?;
|
|
||||||
let mode = if args.is_empty() {
|
|
||||||
OpenMode::ReadText
|
|
||||||
} else {
|
|
||||||
match_open_mode(args.first()?)?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?;
|
Some(FileOpen {
|
||||||
let mode = kw_mode.unwrap_or(mode);
|
|
||||||
resolve_file_open(
|
|
||||||
item,
|
item,
|
||||||
with,
|
filename,
|
||||||
semantic,
|
|
||||||
read_mode,
|
|
||||||
mode,
|
mode,
|
||||||
keywords,
|
keywords,
|
||||||
OpenArgument::Pathlib {
|
reference,
|
||||||
path: attr.value.as_ref(),
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match positional arguments. Return expression for the file name and open mode.
|
/// Match positional arguments. Return expression for the file name and open mode.
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ mod tests {
|
|||||||
use crate::test::test_path;
|
use crate::test::test_path;
|
||||||
use crate::{assert_diagnostics, settings};
|
use crate::{assert_diagnostics, settings};
|
||||||
|
|
||||||
#[test_case(Rule::ReadWholeFile, Path::new("FURB101_0.py"))]
|
#[test_case(Rule::ReadWholeFile, Path::new("FURB101.py"))]
|
||||||
#[test_case(Rule::ReadWholeFile, Path::new("FURB101_1.py"))]
|
|
||||||
#[test_case(Rule::RepeatedAppend, Path::new("FURB113.py"))]
|
#[test_case(Rule::RepeatedAppend, Path::new("FURB113.py"))]
|
||||||
#[test_case(Rule::IfExpInsteadOfOrOperator, Path::new("FURB110.py"))]
|
#[test_case(Rule::IfExpInsteadOfOrOperator, Path::new("FURB110.py"))]
|
||||||
#[test_case(Rule::ReimplementedOperator, Path::new("FURB118.py"))]
|
#[test_case(Rule::ReimplementedOperator, Path::new("FURB118.py"))]
|
||||||
@@ -47,8 +46,7 @@ mod tests {
|
|||||||
#[test_case(Rule::MetaClassABCMeta, Path::new("FURB180.py"))]
|
#[test_case(Rule::MetaClassABCMeta, Path::new("FURB180.py"))]
|
||||||
#[test_case(Rule::HashlibDigestHex, Path::new("FURB181.py"))]
|
#[test_case(Rule::HashlibDigestHex, Path::new("FURB181.py"))]
|
||||||
#[test_case(Rule::ListReverseCopy, Path::new("FURB187.py"))]
|
#[test_case(Rule::ListReverseCopy, Path::new("FURB187.py"))]
|
||||||
#[test_case(Rule::WriteWholeFile, Path::new("FURB103_0.py"))]
|
#[test_case(Rule::WriteWholeFile, Path::new("FURB103.py"))]
|
||||||
#[test_case(Rule::WriteWholeFile, Path::new("FURB103_1.py"))]
|
|
||||||
#[test_case(Rule::FStringNumberFormat, Path::new("FURB116.py"))]
|
#[test_case(Rule::FStringNumberFormat, Path::new("FURB116.py"))]
|
||||||
#[test_case(Rule::SortedMinMax, Path::new("FURB192.py"))]
|
#[test_case(Rule::SortedMinMax, Path::new("FURB192.py"))]
|
||||||
#[test_case(Rule::SliceToRemovePrefixOrSuffix, Path::new("FURB188.py"))]
|
#[test_case(Rule::SliceToRemovePrefixOrSuffix, Path::new("FURB188.py"))]
|
||||||
@@ -67,7 +65,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn write_whole_file_python_39() -> Result<()> {
|
fn write_whole_file_python_39() -> Result<()> {
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
Path::new("refurb/FURB103_0.py"),
|
Path::new("refurb/FURB103.py"),
|
||||||
&settings::LinterSettings::for_rule(Rule::WriteWholeFile)
|
&settings::LinterSettings::for_rule(Rule::WriteWholeFile)
|
||||||
.with_target_version(PythonVersion::PY39),
|
.with_target_version(PythonVersion::PY39),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ruff_text_size::{Ranged, TextRange};
|
|||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::fix::snippet::SourceCodeSnippet;
|
use crate::fix::snippet::SourceCodeSnippet;
|
||||||
use crate::importer::ImportRequest;
|
use crate::importer::ImportRequest;
|
||||||
use crate::rules::refurb::helpers::{FileOpen, OpenArgument, find_file_opens};
|
use crate::rules::refurb::helpers::{FileOpen, find_file_opens};
|
||||||
use crate::{FixAvailability, Violation};
|
use crate::{FixAvailability, Violation};
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
@@ -42,41 +42,27 @@ use crate::{FixAvailability, Violation};
|
|||||||
/// - [Python documentation: `Path.read_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text)
|
/// - [Python documentation: `Path.read_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text)
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
#[violation_metadata(preview_since = "v0.1.2")]
|
#[violation_metadata(preview_since = "v0.1.2")]
|
||||||
pub(crate) struct ReadWholeFile<'a> {
|
pub(crate) struct ReadWholeFile {
|
||||||
filename: SourceCodeSnippet,
|
filename: SourceCodeSnippet,
|
||||||
suggestion: SourceCodeSnippet,
|
suggestion: SourceCodeSnippet,
|
||||||
argument: OpenArgument<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for ReadWholeFile<'_> {
|
impl Violation for ReadWholeFile {
|
||||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let filename = self.filename.truncated_display();
|
let filename = self.filename.truncated_display();
|
||||||
let suggestion = self.suggestion.truncated_display();
|
let suggestion = self.suggestion.truncated_display();
|
||||||
match self.argument {
|
format!("`open` and `read` should be replaced by `Path({filename}).{suggestion}`")
|
||||||
OpenArgument::Pathlib { .. } => {
|
|
||||||
format!(
|
|
||||||
"`Path.open()` followed by `read()` can be replaced by `{filename}.{suggestion}`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
OpenArgument::Builtin { .. } => {
|
|
||||||
format!("`open` and `read` should be replaced by `Path({filename}).{suggestion}`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_title(&self) -> Option<String> {
|
fn fix_title(&self) -> Option<String> {
|
||||||
let filename = self.filename.truncated_display();
|
Some(format!(
|
||||||
let suggestion = self.suggestion.truncated_display();
|
"Replace with `Path({}).{}`",
|
||||||
|
self.filename.truncated_display(),
|
||||||
match self.argument {
|
self.suggestion.truncated_display(),
|
||||||
OpenArgument::Pathlib { .. } => Some(format!("Replace with `{filename}.{suggestion}`")),
|
))
|
||||||
OpenArgument::Builtin { .. } => {
|
|
||||||
Some(format!("Replace with `Path({filename}).{suggestion}`"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +114,13 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> {
|
|||||||
.position(|open| open.is_ref(read_from))
|
.position(|open| open.is_ref(read_from))
|
||||||
{
|
{
|
||||||
let open = self.candidates.remove(open);
|
let open = self.candidates.remove(open);
|
||||||
let filename_display = open.argument.display(self.checker.source());
|
|
||||||
let suggestion = make_suggestion(&open, self.checker.generator());
|
let suggestion = make_suggestion(&open, self.checker.generator());
|
||||||
let mut diagnostic = self.checker.report_diagnostic(
|
let mut diagnostic = self.checker.report_diagnostic(
|
||||||
ReadWholeFile {
|
ReadWholeFile {
|
||||||
filename: SourceCodeSnippet::from_str(filename_display),
|
filename: SourceCodeSnippet::from_str(
|
||||||
|
&self.checker.generator().expr(open.filename),
|
||||||
|
),
|
||||||
suggestion: SourceCodeSnippet::from_str(&suggestion),
|
suggestion: SourceCodeSnippet::from_str(&suggestion),
|
||||||
argument: open.argument,
|
|
||||||
},
|
},
|
||||||
open.item.range(),
|
open.item.range(),
|
||||||
);
|
);
|
||||||
@@ -202,6 +188,8 @@ fn generate_fix(
|
|||||||
|
|
||||||
let locator = checker.locator();
|
let locator = checker.locator();
|
||||||
|
|
||||||
|
let filename_code = locator.slice(open.filename.range());
|
||||||
|
|
||||||
let (import_edit, binding) = checker
|
let (import_edit, binding) = checker
|
||||||
.importer()
|
.importer()
|
||||||
.get_or_import_symbol(
|
.get_or_import_symbol(
|
||||||
@@ -218,15 +206,10 @@ fn generate_fix(
|
|||||||
[Stmt::Assign(ast::StmtAssign { targets, value, .. })] if value.range() == expr.range() => {
|
[Stmt::Assign(ast::StmtAssign { targets, value, .. })] if value.range() == expr.range() => {
|
||||||
match targets.as_slice() {
|
match targets.as_slice() {
|
||||||
[Expr::Name(name)] => {
|
[Expr::Name(name)] => {
|
||||||
let target = match open.argument {
|
format!(
|
||||||
OpenArgument::Builtin { filename } => {
|
"{name} = {binding}({filename_code}).{suggestion}",
|
||||||
let filename_code = locator.slice(filename.range());
|
name = name.id
|
||||||
format!("{binding}({filename_code})")
|
)
|
||||||
}
|
|
||||||
OpenArgument::Pathlib { path } => locator.slice(path.range()).to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
format!("{name} = {target}.{suggestion}", name = name.id)
|
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
@@ -240,16 +223,8 @@ fn generate_fix(
|
|||||||
}),
|
}),
|
||||||
] if value.range() == expr.range() => match target.as_ref() {
|
] if value.range() == expr.range() => match target.as_ref() {
|
||||||
Expr::Name(name) => {
|
Expr::Name(name) => {
|
||||||
let target = match open.argument {
|
|
||||||
OpenArgument::Builtin { filename } => {
|
|
||||||
let filename_code = locator.slice(filename.range());
|
|
||||||
format!("{binding}({filename_code})")
|
|
||||||
}
|
|
||||||
OpenArgument::Pathlib { path } => locator.slice(path.range()).to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"{var}: {ann} = {target}.{suggestion}",
|
"{var}: {ann} = {binding}({filename_code}).{suggestion}",
|
||||||
var = name.id,
|
var = name.id,
|
||||||
ann = locator.slice(annotation.range())
|
ann = locator.slice(annotation.range())
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ fn match_consecutive_appends<'a>(
|
|||||||
let suite = if semantic.at_top_level() {
|
let suite = if semantic.at_top_level() {
|
||||||
// If the statement is at the top level, we should go to the parent module.
|
// If the statement is at the top level, we should go to the parent module.
|
||||||
// Module is available in the definitions list.
|
// Module is available in the definitions list.
|
||||||
EnclosingSuite::new(semantic.definitions.python_ast()?, stmt.into())?
|
EnclosingSuite::new(semantic.definitions.python_ast()?, stmt)?
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, go to the parent, and take its body as a sequence of siblings.
|
// Otherwise, go to the parent, and take its body as a sequence of siblings.
|
||||||
semantic
|
semantic
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ruff_text_size::Ranged;
|
|||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::fix::snippet::SourceCodeSnippet;
|
use crate::fix::snippet::SourceCodeSnippet;
|
||||||
use crate::importer::ImportRequest;
|
use crate::importer::ImportRequest;
|
||||||
use crate::rules::refurb::helpers::{FileOpen, OpenArgument, find_file_opens};
|
use crate::rules::refurb::helpers::{FileOpen, find_file_opens};
|
||||||
use crate::{FixAvailability, Locator, Violation};
|
use crate::{FixAvailability, Locator, Violation};
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
@@ -42,40 +42,26 @@ use crate::{FixAvailability, Locator, Violation};
|
|||||||
/// - [Python documentation: `Path.write_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text)
|
/// - [Python documentation: `Path.write_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text)
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
#[violation_metadata(preview_since = "v0.3.6")]
|
#[violation_metadata(preview_since = "v0.3.6")]
|
||||||
pub(crate) struct WriteWholeFile<'a> {
|
pub(crate) struct WriteWholeFile {
|
||||||
filename: SourceCodeSnippet,
|
filename: SourceCodeSnippet,
|
||||||
suggestion: SourceCodeSnippet,
|
suggestion: SourceCodeSnippet,
|
||||||
argument: OpenArgument<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for WriteWholeFile<'_> {
|
impl Violation for WriteWholeFile {
|
||||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let filename = self.filename.truncated_display();
|
let filename = self.filename.truncated_display();
|
||||||
let suggestion = self.suggestion.truncated_display();
|
let suggestion = self.suggestion.truncated_display();
|
||||||
match self.argument {
|
format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`")
|
||||||
OpenArgument::Pathlib { .. } => {
|
|
||||||
format!(
|
|
||||||
"`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
OpenArgument::Builtin { .. } => {
|
|
||||||
format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn fix_title(&self) -> Option<String> {
|
fn fix_title(&self) -> Option<String> {
|
||||||
let filename = self.filename.truncated_display();
|
Some(format!(
|
||||||
let suggestion = self.suggestion.truncated_display();
|
"Replace with `Path({}).{}`",
|
||||||
|
self.filename.truncated_display(),
|
||||||
match self.argument {
|
self.suggestion.truncated_display(),
|
||||||
OpenArgument::Pathlib { .. } => Some(format!("Replace with `{filename}.{suggestion}`")),
|
))
|
||||||
OpenArgument::Builtin { .. } => {
|
|
||||||
Some(format!("Replace with `Path({filename}).{suggestion}`"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,15 +125,16 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> {
|
|||||||
.position(|open| open.is_ref(write_to))
|
.position(|open| open.is_ref(write_to))
|
||||||
{
|
{
|
||||||
let open = self.candidates.remove(open);
|
let open = self.candidates.remove(open);
|
||||||
|
|
||||||
if self.loop_counter == 0 {
|
if self.loop_counter == 0 {
|
||||||
let filename_display = open.argument.display(self.checker.source());
|
|
||||||
let suggestion = make_suggestion(&open, content, self.checker.locator());
|
let suggestion = make_suggestion(&open, content, self.checker.locator());
|
||||||
|
|
||||||
let mut diagnostic = self.checker.report_diagnostic(
|
let mut diagnostic = self.checker.report_diagnostic(
|
||||||
WriteWholeFile {
|
WriteWholeFile {
|
||||||
filename: SourceCodeSnippet::from_str(filename_display),
|
filename: SourceCodeSnippet::from_str(
|
||||||
|
&self.checker.generator().expr(open.filename),
|
||||||
|
),
|
||||||
suggestion: SourceCodeSnippet::from_str(&suggestion),
|
suggestion: SourceCodeSnippet::from_str(&suggestion),
|
||||||
argument: open.argument,
|
|
||||||
},
|
},
|
||||||
open.item.range(),
|
open.item.range(),
|
||||||
);
|
);
|
||||||
@@ -211,6 +198,7 @@ fn generate_fix(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let locator = checker.locator();
|
let locator = checker.locator();
|
||||||
|
let filename_code = locator.slice(open.filename.range());
|
||||||
|
|
||||||
let (import_edit, binding) = checker
|
let (import_edit, binding) = checker
|
||||||
.importer()
|
.importer()
|
||||||
@@ -221,15 +209,7 @@ fn generate_fix(
|
|||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let target = match open.argument {
|
let replacement = format!("{binding}({filename_code}).{suggestion}");
|
||||||
OpenArgument::Builtin { filename } => {
|
|
||||||
let filename_code = locator.slice(filename.range());
|
|
||||||
format!("{binding}({filename_code})")
|
|
||||||
}
|
|
||||||
OpenArgument::Pathlib { path } => locator.slice(path.range()).to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let replacement = format!("{target}.{suggestion}");
|
|
||||||
|
|
||||||
let applicability = if checker.comment_ranges().intersects(with_stmt.range()) {
|
let applicability = if checker.comment_ranges().intersects(with_stmt.range()) {
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||||
---
|
---
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
||||||
--> FURB101_0.py:12:6
|
--> FURB101.py:12:6
|
||||||
|
|
|
|
||||||
11 | # FURB101
|
11 | # FURB101
|
||||||
12 | with open("file.txt") as f:
|
12 | with open("file.txt") as f:
|
||||||
@@ -26,7 +26,7 @@ help: Replace with `Path("file.txt").read_text()`
|
|||||||
16 | with open("file.txt", "rb") as f:
|
16 | with open("file.txt", "rb") as f:
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_bytes()`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_bytes()`
|
||||||
--> FURB101_0.py:16:6
|
--> FURB101.py:16:6
|
||||||
|
|
|
|
||||||
15 | # FURB101
|
15 | # FURB101
|
||||||
16 | with open("file.txt", "rb") as f:
|
16 | with open("file.txt", "rb") as f:
|
||||||
@@ -50,7 +50,7 @@ help: Replace with `Path("file.txt").read_bytes()`
|
|||||||
20 | with open("file.txt", mode="rb") as f:
|
20 | with open("file.txt", mode="rb") as f:
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_bytes()`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_bytes()`
|
||||||
--> FURB101_0.py:20:6
|
--> FURB101.py:20:6
|
||||||
|
|
|
|
||||||
19 | # FURB101
|
19 | # FURB101
|
||||||
20 | with open("file.txt", mode="rb") as f:
|
20 | with open("file.txt", mode="rb") as f:
|
||||||
@@ -74,7 +74,7 @@ help: Replace with `Path("file.txt").read_bytes()`
|
|||||||
24 | with open("file.txt", encoding="utf8") as f:
|
24 | with open("file.txt", encoding="utf8") as f:
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf8")`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf8")`
|
||||||
--> FURB101_0.py:24:6
|
--> FURB101.py:24:6
|
||||||
|
|
|
|
||||||
23 | # FURB101
|
23 | # FURB101
|
||||||
24 | with open("file.txt", encoding="utf8") as f:
|
24 | with open("file.txt", encoding="utf8") as f:
|
||||||
@@ -98,7 +98,7 @@ help: Replace with `Path("file.txt").read_text(encoding="utf8")`
|
|||||||
28 | with open("file.txt", errors="ignore") as f:
|
28 | with open("file.txt", errors="ignore") as f:
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(errors="ignore")`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(errors="ignore")`
|
||||||
--> FURB101_0.py:28:6
|
--> FURB101.py:28:6
|
||||||
|
|
|
|
||||||
27 | # FURB101
|
27 | # FURB101
|
||||||
28 | with open("file.txt", errors="ignore") as f:
|
28 | with open("file.txt", errors="ignore") as f:
|
||||||
@@ -122,7 +122,7 @@ help: Replace with `Path("file.txt").read_text(errors="ignore")`
|
|||||||
32 | with open("file.txt", mode="r") as f: # noqa: FURB120
|
32 | with open("file.txt", mode="r") as f: # noqa: FURB120
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
||||||
--> FURB101_0.py:32:6
|
--> FURB101.py:32:6
|
||||||
|
|
|
|
||||||
31 | # FURB101
|
31 | # FURB101
|
||||||
32 | with open("file.txt", mode="r") as f: # noqa: FURB120
|
32 | with open("file.txt", mode="r") as f: # noqa: FURB120
|
||||||
@@ -147,7 +147,7 @@ help: Replace with `Path("file.txt").read_text()`
|
|||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path(foo()).read_bytes()`
|
FURB101 `open` and `read` should be replaced by `Path(foo()).read_bytes()`
|
||||||
--> FURB101_0.py:36:6
|
--> FURB101.py:36:6
|
||||||
|
|
|
|
||||||
35 | # FURB101
|
35 | # FURB101
|
||||||
36 | with open(foo(), "rb") as f:
|
36 | with open(foo(), "rb") as f:
|
||||||
@@ -158,7 +158,7 @@ FURB101 `open` and `read` should be replaced by `Path(foo()).read_bytes()`
|
|||||||
help: Replace with `Path(foo()).read_bytes()`
|
help: Replace with `Path(foo()).read_bytes()`
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("a.txt").read_text()`
|
FURB101 `open` and `read` should be replaced by `Path("a.txt").read_text()`
|
||||||
--> FURB101_0.py:44:6
|
--> FURB101.py:44:6
|
||||||
|
|
|
|
||||||
43 | # FURB101
|
43 | # FURB101
|
||||||
44 | with open("a.txt") as a, open("b.txt", "rb") as b:
|
44 | with open("a.txt") as a, open("b.txt", "rb") as b:
|
||||||
@@ -169,7 +169,7 @@ FURB101 `open` and `read` should be replaced by `Path("a.txt").read_text()`
|
|||||||
help: Replace with `Path("a.txt").read_text()`
|
help: Replace with `Path("a.txt").read_text()`
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("b.txt").read_bytes()`
|
FURB101 `open` and `read` should be replaced by `Path("b.txt").read_bytes()`
|
||||||
--> FURB101_0.py:44:26
|
--> FURB101.py:44:26
|
||||||
|
|
|
|
||||||
43 | # FURB101
|
43 | # FURB101
|
||||||
44 | with open("a.txt") as a, open("b.txt", "rb") as b:
|
44 | with open("a.txt") as a, open("b.txt", "rb") as b:
|
||||||
@@ -180,7 +180,7 @@ FURB101 `open` and `read` should be replaced by `Path("b.txt").read_bytes()`
|
|||||||
help: Replace with `Path("b.txt").read_bytes()`
|
help: Replace with `Path("b.txt").read_bytes()`
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
||||||
--> FURB101_0.py:49:18
|
--> FURB101.py:49:18
|
||||||
|
|
|
|
||||||
48 | # FURB101
|
48 | # FURB101
|
||||||
49 | with foo() as a, open("file.txt") as b, foo() as c:
|
49 | with foo() as a, open("file.txt") as b, foo() as c:
|
||||||
@@ -191,7 +191,7 @@ FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text()`
|
|||||||
help: Replace with `Path("file.txt").read_text()`
|
help: Replace with `Path("file.txt").read_text()`
|
||||||
|
|
||||||
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
FURB101 [*] `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
--> FURB101_0.py:130:6
|
--> FURB101.py:130:6
|
||||||
|
|
|
|
||||||
129 | # FURB101
|
129 | # FURB101
|
||||||
130 | with open("file.txt", encoding="utf-8") as f:
|
130 | with open("file.txt", encoding="utf-8") as f:
|
||||||
@@ -215,7 +215,7 @@ help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
|||||||
134 | with open("file.txt", encoding="utf-8") as f:
|
134 | with open("file.txt", encoding="utf-8") as f:
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
--> FURB101_0.py:134:6
|
--> FURB101.py:134:6
|
||||||
|
|
|
|
||||||
133 | # FURB101 but no fix because it would remove the assignment to `x`
|
133 | # FURB101 but no fix because it would remove the assignment to `x`
|
||||||
134 | with open("file.txt", encoding="utf-8") as f:
|
134 | with open("file.txt", encoding="utf-8") as f:
|
||||||
@@ -225,7 +225,7 @@ FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(enco
|
|||||||
help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
--> FURB101_0.py:138:6
|
--> FURB101.py:138:6
|
||||||
|
|
|
|
||||||
137 | # FURB101 but no fix because it would remove the `process_contents` call
|
137 | # FURB101 but no fix because it would remove the `process_contents` call
|
||||||
138 | with open("file.txt", encoding="utf-8") as f:
|
138 | with open("file.txt", encoding="utf-8") as f:
|
||||||
@@ -234,13 +234,13 @@ FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(enco
|
|||||||
|
|
|
|
||||||
help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
|
|
||||||
FURB101 `open` and `read` should be replaced by `Path("file1.txt").read_text(encoding="utf-8")`
|
FURB101 `open` and `read` should be replaced by `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
--> FURB101_0.py:141:6
|
--> FURB101.py:141:6
|
||||||
|
|
|
|
||||||
139 | contents = process_contents(f.read())
|
139 | contents = process_contents(f.read())
|
||||||
140 |
|
140 |
|
||||||
141 | with open("file1.txt", encoding="utf-8") as f:
|
141 | with open("file.txt", encoding="utf-8") as f:
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
142 | contents: str = process_contents(f.read())
|
142 | contents: str = process_contents(f.read())
|
||||||
|
|
|
|
||||||
help: Replace with `Path("file1.txt").read_text(encoding="utf-8")`
|
help: Replace with `Path("file.txt").read_text(encoding="utf-8")`
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
|
||||||
---
|
|
||||||
FURB101 [*] `Path.open()` followed by `read()` can be replaced by `Path("file.txt").read_text()`
|
|
||||||
--> FURB101_1.py:4:6
|
|
||||||
|
|
|
||||||
2 | from pathlib import Path
|
|
||||||
3 |
|
|
||||||
4 | with Path("file.txt").open() as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
5 | contents = f.read()
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").read_text()`
|
|
||||||
1 |
|
|
||||||
2 | from pathlib import Path
|
|
||||||
3 |
|
|
||||||
- with Path("file.txt").open() as f:
|
|
||||||
- contents = f.read()
|
|
||||||
4 + contents = Path("file.txt").read_text()
|
|
||||||
5 |
|
|
||||||
6 | with Path("file.txt").open("r") as f:
|
|
||||||
7 | contents = f.read()
|
|
||||||
|
|
||||||
FURB101 [*] `Path.open()` followed by `read()` can be replaced by `Path("file.txt").read_text()`
|
|
||||||
--> FURB101_1.py:7:6
|
|
||||||
|
|
|
||||||
5 | contents = f.read()
|
|
||||||
6 |
|
|
||||||
7 | with Path("file.txt").open("r") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
8 | contents = f.read()
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").read_text()`
|
|
||||||
4 | with Path("file.txt").open() as f:
|
|
||||||
5 | contents = f.read()
|
|
||||||
6 |
|
|
||||||
- with Path("file.txt").open("r") as f:
|
|
||||||
- contents = f.read()
|
|
||||||
7 + contents = Path("file.txt").read_text()
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||||
---
|
---
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")`
|
||||||
--> FURB103_0.py:12:6
|
--> FURB103.py:12:6
|
||||||
|
|
|
|
||||||
11 | # FURB103
|
11 | # FURB103
|
||||||
12 | with open("file.txt", "w") as f:
|
12 | with open("file.txt", "w") as f:
|
||||||
@@ -26,7 +26,7 @@ help: Replace with `Path("file.txt").write_text("test")`
|
|||||||
16 | with open("file.txt", "wb") as f:
|
16 | with open("file.txt", "wb") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)`
|
||||||
--> FURB103_0.py:16:6
|
--> FURB103.py:16:6
|
||||||
|
|
|
|
||||||
15 | # FURB103
|
15 | # FURB103
|
||||||
16 | with open("file.txt", "wb") as f:
|
16 | with open("file.txt", "wb") as f:
|
||||||
@@ -50,7 +50,7 @@ help: Replace with `Path("file.txt").write_bytes(foobar)`
|
|||||||
20 | with open("file.txt", mode="wb") as f:
|
20 | with open("file.txt", mode="wb") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")`
|
||||||
--> FURB103_0.py:20:6
|
--> FURB103.py:20:6
|
||||||
|
|
|
|
||||||
19 | # FURB103
|
19 | # FURB103
|
||||||
20 | with open("file.txt", mode="wb") as f:
|
20 | with open("file.txt", mode="wb") as f:
|
||||||
@@ -74,7 +74,7 @@ help: Replace with `Path("file.txt").write_bytes(b"abc")`
|
|||||||
24 | with open("file.txt", "w", encoding="utf8") as f:
|
24 | with open("file.txt", "w", encoding="utf8") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")`
|
||||||
--> FURB103_0.py:24:6
|
--> FURB103.py:24:6
|
||||||
|
|
|
|
||||||
23 | # FURB103
|
23 | # FURB103
|
||||||
24 | with open("file.txt", "w", encoding="utf8") as f:
|
24 | with open("file.txt", "w", encoding="utf8") as f:
|
||||||
@@ -98,7 +98,7 @@ help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")`
|
|||||||
28 | with open("file.txt", "w", errors="ignore") as f:
|
28 | with open("file.txt", "w", errors="ignore") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")`
|
||||||
--> FURB103_0.py:28:6
|
--> FURB103.py:28:6
|
||||||
|
|
|
|
||||||
27 | # FURB103
|
27 | # FURB103
|
||||||
28 | with open("file.txt", "w", errors="ignore") as f:
|
28 | with open("file.txt", "w", errors="ignore") as f:
|
||||||
@@ -122,7 +122,7 @@ help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")`
|
|||||||
32 | with open("file.txt", mode="w") as f:
|
32 | with open("file.txt", mode="w") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)`
|
||||||
--> FURB103_0.py:32:6
|
--> FURB103.py:32:6
|
||||||
|
|
|
|
||||||
31 | # FURB103
|
31 | # FURB103
|
||||||
32 | with open("file.txt", mode="w") as f:
|
32 | with open("file.txt", mode="w") as f:
|
||||||
@@ -146,7 +146,7 @@ help: Replace with `Path("file.txt").write_text(foobar)`
|
|||||||
36 | with open(foo(), "wb") as f:
|
36 | with open(foo(), "wb") as f:
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())`
|
FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())`
|
||||||
--> FURB103_0.py:36:6
|
--> FURB103.py:36:6
|
||||||
|
|
|
|
||||||
35 | # FURB103
|
35 | # FURB103
|
||||||
36 | with open(foo(), "wb") as f:
|
36 | with open(foo(), "wb") as f:
|
||||||
@@ -157,7 +157,7 @@ FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())
|
|||||||
help: Replace with `Path(foo()).write_bytes(bar())`
|
help: Replace with `Path(foo()).write_bytes(bar())`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
||||||
--> FURB103_0.py:44:6
|
--> FURB103.py:44:6
|
||||||
|
|
|
|
||||||
43 | # FURB103
|
43 | # FURB103
|
||||||
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||||
@@ -168,7 +168,7 @@ FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
|||||||
help: Replace with `Path("a.txt").write_text(x)`
|
help: Replace with `Path("a.txt").write_text(x)`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
||||||
--> FURB103_0.py:44:31
|
--> FURB103.py:44:31
|
||||||
|
|
|
|
||||||
43 | # FURB103
|
43 | # FURB103
|
||||||
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||||
@@ -179,7 +179,7 @@ FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
|||||||
help: Replace with `Path("b.txt").write_bytes(y)`
|
help: Replace with `Path("b.txt").write_bytes(y)`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))`
|
FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))`
|
||||||
--> FURB103_0.py:49:18
|
--> FURB103.py:49:18
|
||||||
|
|
|
|
||||||
48 | # FURB103
|
48 | # FURB103
|
||||||
49 | with foo() as a, open("file.txt", "w") as b, foo() as c:
|
49 | with foo() as a, open("file.txt", "w") as b, foo() as c:
|
||||||
@@ -190,7 +190,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba
|
|||||||
help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))`
|
help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))`
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
||||||
--> FURB103_0.py:58:6
|
--> FURB103.py:58:6
|
||||||
|
|
|
|
||||||
57 | # FURB103
|
57 | # FURB103
|
||||||
58 | with open("file.txt", "w", newline="\r\n") as f:
|
58 | with open("file.txt", "w", newline="\r\n") as f:
|
||||||
@@ -214,7 +214,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")`
|
|||||||
62 | import builtins
|
62 | import builtins
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
||||||
--> FURB103_0.py:66:6
|
--> FURB103.py:66:6
|
||||||
|
|
|
|
||||||
65 | # FURB103
|
65 | # FURB103
|
||||||
66 | with builtins.open("file.txt", "w", newline="\r\n") as f:
|
66 | with builtins.open("file.txt", "w", newline="\r\n") as f:
|
||||||
@@ -237,7 +237,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")`
|
|||||||
70 | from builtins import open as o
|
70 | from builtins import open as o
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")`
|
||||||
--> FURB103_0.py:74:6
|
--> FURB103.py:74:6
|
||||||
|
|
|
|
||||||
73 | # FURB103
|
73 | # FURB103
|
||||||
74 | with o("file.txt", "w", newline="\r\n") as f:
|
74 | with o("file.txt", "w", newline="\r\n") as f:
|
||||||
@@ -260,7 +260,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")`
|
|||||||
78 |
|
78 |
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....`
|
FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....`
|
||||||
--> FURB103_0.py:154:6
|
--> FURB103.py:154:6
|
||||||
|
|
|
|
||||||
152 | data = {"price": 100}
|
152 | data = {"price": 100}
|
||||||
153 |
|
153 |
|
||||||
@@ -284,7 +284,7 @@ help: Replace with `Path("test.json")....`
|
|||||||
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("tmp_path/pyproject.toml")....`
|
FURB103 [*] `open` and `write` should be replaced by `Path("tmp_path/pyproject.toml")....`
|
||||||
--> FURB103_0.py:158:6
|
--> FURB103.py:158:6
|
||||||
|
|
|
|
||||||
157 | # See: https://github.com/astral-sh/ruff/issues/21381
|
157 | # See: https://github.com/astral-sh/ruff/issues/21381
|
||||||
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
|
||||||
---
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")`
|
|
||||||
--> FURB103_1.py:3:6
|
|
||||||
|
|
|
||||||
1 | from pathlib import Path
|
|
||||||
2 |
|
|
||||||
3 | with Path("file.txt").open("w") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
4 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").write_text("test")`
|
|
||||||
1 | from pathlib import Path
|
|
||||||
2 |
|
|
||||||
- with Path("file.txt").open("w") as f:
|
|
||||||
- f.write("test")
|
|
||||||
3 + Path("file.txt").write_text("test")
|
|
||||||
4 |
|
|
||||||
5 | with Path("file.txt").open("wb") as f:
|
|
||||||
6 | f.write(b"test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")`
|
|
||||||
--> FURB103_1.py:6:6
|
|
||||||
|
|
|
||||||
4 | f.write("test")
|
|
||||||
5 |
|
|
||||||
6 | with Path("file.txt").open("wb") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
7 | f.write(b"test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").write_bytes(b"test")`
|
|
||||||
3 | with Path("file.txt").open("w") as f:
|
|
||||||
4 | f.write("test")
|
|
||||||
5 |
|
|
||||||
- with Path("file.txt").open("wb") as f:
|
|
||||||
- f.write(b"test")
|
|
||||||
6 + Path("file.txt").write_bytes(b"test")
|
|
||||||
7 |
|
|
||||||
8 | with Path("file.txt").open(mode="w") as f:
|
|
||||||
9 | f.write("test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")`
|
|
||||||
--> FURB103_1.py:9:6
|
|
||||||
|
|
|
||||||
7 | f.write(b"test")
|
|
||||||
8 |
|
|
||||||
9 | with Path("file.txt").open(mode="w") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
10 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").write_text("test")`
|
|
||||||
6 | with Path("file.txt").open("wb") as f:
|
|
||||||
7 | f.write(b"test")
|
|
||||||
8 |
|
|
||||||
- with Path("file.txt").open(mode="w") as f:
|
|
||||||
- f.write("test")
|
|
||||||
9 + Path("file.txt").write_text("test")
|
|
||||||
10 |
|
|
||||||
11 | with Path("file.txt").open("w", encoding="utf8") as f:
|
|
||||||
12 | f.write("test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")`
|
|
||||||
--> FURB103_1.py:12:6
|
|
||||||
|
|
|
||||||
10 | f.write("test")
|
|
||||||
11 |
|
|
||||||
12 | with Path("file.txt").open("w", encoding="utf8") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
13 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").write_text("test", encoding="utf8")`
|
|
||||||
9 | with Path("file.txt").open(mode="w") as f:
|
|
||||||
10 | f.write("test")
|
|
||||||
11 |
|
|
||||||
- with Path("file.txt").open("w", encoding="utf8") as f:
|
|
||||||
- f.write("test")
|
|
||||||
12 + Path("file.txt").write_text("test", encoding="utf8")
|
|
||||||
13 |
|
|
||||||
14 | with Path("file.txt").open("w", errors="ignore") as f:
|
|
||||||
15 | f.write("test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")`
|
|
||||||
--> FURB103_1.py:15:6
|
|
||||||
|
|
|
||||||
13 | f.write("test")
|
|
||||||
14 |
|
|
||||||
15 | with Path("file.txt").open("w", errors="ignore") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
16 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("file.txt").write_text("test", errors="ignore")`
|
|
||||||
12 | with Path("file.txt").open("w", encoding="utf8") as f:
|
|
||||||
13 | f.write("test")
|
|
||||||
14 |
|
|
||||||
- with Path("file.txt").open("w", errors="ignore") as f:
|
|
||||||
- f.write("test")
|
|
||||||
15 + Path("file.txt").write_text("test", errors="ignore")
|
|
||||||
16 |
|
|
||||||
17 | with Path(foo()).open("w") as f:
|
|
||||||
18 | f.write("test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")`
|
|
||||||
--> FURB103_1.py:18:6
|
|
||||||
|
|
|
||||||
16 | f.write("test")
|
|
||||||
17 |
|
|
||||||
18 | with Path(foo()).open("w") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
19 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path(foo()).write_text("test")`
|
|
||||||
15 | with Path("file.txt").open("w", errors="ignore") as f:
|
|
||||||
16 | f.write("test")
|
|
||||||
17 |
|
|
||||||
- with Path(foo()).open("w") as f:
|
|
||||||
- f.write("test")
|
|
||||||
18 + Path(foo()).write_text("test")
|
|
||||||
19 |
|
|
||||||
20 | p = Path("file.txt")
|
|
||||||
21 | with p.open("w") as f:
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `p.write_text("test")`
|
|
||||||
--> FURB103_1.py:22:6
|
|
||||||
|
|
|
||||||
21 | p = Path("file.txt")
|
|
||||||
22 | with p.open("w") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
23 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `p.write_text("test")`
|
|
||||||
19 | f.write("test")
|
|
||||||
20 |
|
|
||||||
21 | p = Path("file.txt")
|
|
||||||
- with p.open("w") as f:
|
|
||||||
- f.write("test")
|
|
||||||
22 + p.write_text("test")
|
|
||||||
23 |
|
|
||||||
24 | with Path("foo", "bar", "baz").open("w") as f:
|
|
||||||
25 | f.write("test")
|
|
||||||
|
|
||||||
FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("foo", "bar", "baz").write_text("test")`
|
|
||||||
--> FURB103_1.py:25:6
|
|
||||||
|
|
|
||||||
23 | f.write("test")
|
|
||||||
24 |
|
|
||||||
25 | with Path("foo", "bar", "baz").open("w") as f:
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
26 | f.write("test")
|
|
||||||
|
|
|
||||||
help: Replace with `Path("foo", "bar", "baz").write_text("test")`
|
|
||||||
22 | with p.open("w") as f:
|
|
||||||
23 | f.write("test")
|
|
||||||
24 |
|
|
||||||
- with Path("foo", "bar", "baz").open("w") as f:
|
|
||||||
- f.write("test")
|
|
||||||
25 + Path("foo", "bar", "baz").write_text("test")
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||||
---
|
---
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")`
|
||||||
--> FURB103_0.py:12:6
|
--> FURB103.py:12:6
|
||||||
|
|
|
|
||||||
11 | # FURB103
|
11 | # FURB103
|
||||||
12 | with open("file.txt", "w") as f:
|
12 | with open("file.txt", "w") as f:
|
||||||
@@ -26,7 +26,7 @@ help: Replace with `Path("file.txt").write_text("test")`
|
|||||||
16 | with open("file.txt", "wb") as f:
|
16 | with open("file.txt", "wb") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)`
|
||||||
--> FURB103_0.py:16:6
|
--> FURB103.py:16:6
|
||||||
|
|
|
|
||||||
15 | # FURB103
|
15 | # FURB103
|
||||||
16 | with open("file.txt", "wb") as f:
|
16 | with open("file.txt", "wb") as f:
|
||||||
@@ -50,7 +50,7 @@ help: Replace with `Path("file.txt").write_bytes(foobar)`
|
|||||||
20 | with open("file.txt", mode="wb") as f:
|
20 | with open("file.txt", mode="wb") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")`
|
||||||
--> FURB103_0.py:20:6
|
--> FURB103.py:20:6
|
||||||
|
|
|
|
||||||
19 | # FURB103
|
19 | # FURB103
|
||||||
20 | with open("file.txt", mode="wb") as f:
|
20 | with open("file.txt", mode="wb") as f:
|
||||||
@@ -74,7 +74,7 @@ help: Replace with `Path("file.txt").write_bytes(b"abc")`
|
|||||||
24 | with open("file.txt", "w", encoding="utf8") as f:
|
24 | with open("file.txt", "w", encoding="utf8") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")`
|
||||||
--> FURB103_0.py:24:6
|
--> FURB103.py:24:6
|
||||||
|
|
|
|
||||||
23 | # FURB103
|
23 | # FURB103
|
||||||
24 | with open("file.txt", "w", encoding="utf8") as f:
|
24 | with open("file.txt", "w", encoding="utf8") as f:
|
||||||
@@ -98,7 +98,7 @@ help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")`
|
|||||||
28 | with open("file.txt", "w", errors="ignore") as f:
|
28 | with open("file.txt", "w", errors="ignore") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")`
|
||||||
--> FURB103_0.py:28:6
|
--> FURB103.py:28:6
|
||||||
|
|
|
|
||||||
27 | # FURB103
|
27 | # FURB103
|
||||||
28 | with open("file.txt", "w", errors="ignore") as f:
|
28 | with open("file.txt", "w", errors="ignore") as f:
|
||||||
@@ -122,7 +122,7 @@ help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")`
|
|||||||
32 | with open("file.txt", mode="w") as f:
|
32 | with open("file.txt", mode="w") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)`
|
FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)`
|
||||||
--> FURB103_0.py:32:6
|
--> FURB103.py:32:6
|
||||||
|
|
|
|
||||||
31 | # FURB103
|
31 | # FURB103
|
||||||
32 | with open("file.txt", mode="w") as f:
|
32 | with open("file.txt", mode="w") as f:
|
||||||
@@ -146,7 +146,7 @@ help: Replace with `Path("file.txt").write_text(foobar)`
|
|||||||
36 | with open(foo(), "wb") as f:
|
36 | with open(foo(), "wb") as f:
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())`
|
FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())`
|
||||||
--> FURB103_0.py:36:6
|
--> FURB103.py:36:6
|
||||||
|
|
|
|
||||||
35 | # FURB103
|
35 | # FURB103
|
||||||
36 | with open(foo(), "wb") as f:
|
36 | with open(foo(), "wb") as f:
|
||||||
@@ -157,7 +157,7 @@ FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())
|
|||||||
help: Replace with `Path(foo()).write_bytes(bar())`
|
help: Replace with `Path(foo()).write_bytes(bar())`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
||||||
--> FURB103_0.py:44:6
|
--> FURB103.py:44:6
|
||||||
|
|
|
|
||||||
43 | # FURB103
|
43 | # FURB103
|
||||||
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||||
@@ -168,7 +168,7 @@ FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)`
|
|||||||
help: Replace with `Path("a.txt").write_text(x)`
|
help: Replace with `Path("a.txt").write_text(x)`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
||||||
--> FURB103_0.py:44:31
|
--> FURB103.py:44:31
|
||||||
|
|
|
|
||||||
43 | # FURB103
|
43 | # FURB103
|
||||||
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||||
@@ -179,7 +179,7 @@ FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)`
|
|||||||
help: Replace with `Path("b.txt").write_bytes(y)`
|
help: Replace with `Path("b.txt").write_bytes(y)`
|
||||||
|
|
||||||
FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))`
|
FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))`
|
||||||
--> FURB103_0.py:49:18
|
--> FURB103.py:49:18
|
||||||
|
|
|
|
||||||
48 | # FURB103
|
48 | # FURB103
|
||||||
49 | with foo() as a, open("file.txt", "w") as b, foo() as c:
|
49 | with foo() as a, open("file.txt", "w") as b, foo() as c:
|
||||||
@@ -190,7 +190,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba
|
|||||||
help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))`
|
help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))`
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....`
|
FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....`
|
||||||
--> FURB103_0.py:154:6
|
--> FURB103.py:154:6
|
||||||
|
|
|
|
||||||
152 | data = {"price": 100}
|
152 | data = {"price": 100}
|
||||||
153 |
|
153 |
|
||||||
@@ -214,7 +214,7 @@ help: Replace with `Path("test.json")....`
|
|||||||
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
||||||
|
|
||||||
FURB103 [*] `open` and `write` should be replaced by `Path("tmp_path/pyproject.toml")....`
|
FURB103 [*] `open` and `write` should be replaced by `Path("tmp_path/pyproject.toml")....`
|
||||||
--> FURB103_0.py:158:6
|
--> FURB103.py:158:6
|
||||||
|
|
|
|
||||||
157 | # See: https://github.com/astral-sh/ruff/issues/21381
|
157 | # See: https://github.com/astral-sh/ruff/issues/21381
|
||||||
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
158 | with open("tmp_path/pyproject.toml", "w") as f:
|
||||||
|
|||||||
@@ -313,20 +313,12 @@ mod tests {
|
|||||||
Rule::UnusedVariable,
|
Rule::UnusedVariable,
|
||||||
Rule::AmbiguousVariableName,
|
Rule::AmbiguousVariableName,
|
||||||
Rule::UnusedNOQA,
|
Rule::UnusedNOQA,
|
||||||
Rule::InvalidRuleCode,
|
]),
|
||||||
Rule::InvalidSuppressionComment,
|
|
||||||
Rule::UnmatchedSuppressionComment,
|
|
||||||
])
|
|
||||||
.with_external_rules(&["TK421"]),
|
|
||||||
&settings::LinterSettings::for_rules(vec![
|
&settings::LinterSettings::for_rules(vec![
|
||||||
Rule::UnusedVariable,
|
Rule::UnusedVariable,
|
||||||
Rule::AmbiguousVariableName,
|
Rule::AmbiguousVariableName,
|
||||||
Rule::UnusedNOQA,
|
Rule::UnusedNOQA,
|
||||||
Rule::InvalidRuleCode,
|
|
||||||
Rule::InvalidSuppressionComment,
|
|
||||||
Rule::UnmatchedSuppressionComment,
|
|
||||||
])
|
])
|
||||||
.with_external_rules(&["TK421"])
|
|
||||||
.with_preview_mode(),
|
.with_preview_mode(),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -9,21 +9,6 @@ use crate::registry::Rule;
|
|||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rule_redirects::get_redirect_target;
|
||||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) enum InvalidRuleCodeKind {
|
|
||||||
Noqa,
|
|
||||||
Suppression,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InvalidRuleCodeKind {
|
|
||||||
fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
InvalidRuleCodeKind::Noqa => "`# noqa`",
|
|
||||||
InvalidRuleCodeKind::Suppression => "suppression",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for `noqa` codes that are invalid.
|
/// Checks for `noqa` codes that are invalid.
|
||||||
///
|
///
|
||||||
@@ -51,17 +36,12 @@ impl InvalidRuleCodeKind {
|
|||||||
#[violation_metadata(preview_since = "0.11.4")]
|
#[violation_metadata(preview_since = "0.11.4")]
|
||||||
pub(crate) struct InvalidRuleCode {
|
pub(crate) struct InvalidRuleCode {
|
||||||
pub(crate) rule_code: String,
|
pub(crate) rule_code: String,
|
||||||
pub(crate) kind: InvalidRuleCodeKind,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlwaysFixableViolation for InvalidRuleCode {
|
impl AlwaysFixableViolation for InvalidRuleCode {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!(
|
format!("Invalid rule code in `# noqa`: {}", self.rule_code)
|
||||||
"Invalid rule code in {}: {}",
|
|
||||||
self.kind.as_str(),
|
|
||||||
self.rule_code
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_title(&self) -> String {
|
fn fix_title(&self) -> String {
|
||||||
@@ -81,9 +61,7 @@ pub(crate) fn invalid_noqa_code(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let all_valid = directive
|
let all_valid = directive.iter().all(|code| code_is_valid(code, external));
|
||||||
.iter()
|
|
||||||
.all(|code| code_is_valid(code.as_str(), external));
|
|
||||||
|
|
||||||
if all_valid {
|
if all_valid {
|
||||||
continue;
|
continue;
|
||||||
@@ -91,7 +69,7 @@ pub(crate) fn invalid_noqa_code(
|
|||||||
|
|
||||||
let (valid_codes, invalid_codes): (Vec<_>, Vec<_>) = directive
|
let (valid_codes, invalid_codes): (Vec<_>, Vec<_>) = directive
|
||||||
.iter()
|
.iter()
|
||||||
.partition(|&code| code_is_valid(code.as_str(), external));
|
.partition(|&code| code_is_valid(code, external));
|
||||||
|
|
||||||
if valid_codes.is_empty() {
|
if valid_codes.is_empty() {
|
||||||
all_codes_invalid_diagnostic(directive, invalid_codes, context);
|
all_codes_invalid_diagnostic(directive, invalid_codes, context);
|
||||||
@@ -103,9 +81,10 @@ pub(crate) fn invalid_noqa_code(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn code_is_valid(code: &str, external: &[String]) -> bool {
|
fn code_is_valid(code: &Code, external: &[String]) -> bool {
|
||||||
Rule::from_code(get_redirect_target(code).unwrap_or(code)).is_ok()
|
let code_str = code.as_str();
|
||||||
|| external.iter().any(|ext| code.starts_with(ext))
|
Rule::from_code(get_redirect_target(code_str).unwrap_or(code_str)).is_ok()
|
||||||
|
|| external.iter().any(|ext| code_str.starts_with(ext))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_codes_invalid_diagnostic(
|
fn all_codes_invalid_diagnostic(
|
||||||
@@ -121,7 +100,6 @@ fn all_codes_invalid_diagnostic(
|
|||||||
.map(Code::as_str)
|
.map(Code::as_str)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
kind: InvalidRuleCodeKind::Noqa,
|
|
||||||
},
|
},
|
||||||
directive.range(),
|
directive.range(),
|
||||||
)
|
)
|
||||||
@@ -138,7 +116,6 @@ fn some_codes_are_invalid_diagnostic(
|
|||||||
.report_diagnostic(
|
.report_diagnostic(
|
||||||
InvalidRuleCode {
|
InvalidRuleCode {
|
||||||
rule_code: invalid_code.to_string(),
|
rule_code: invalid_code.to_string(),
|
||||||
kind: InvalidRuleCodeKind::Noqa,
|
|
||||||
},
|
},
|
||||||
invalid_code.range(),
|
invalid_code.range(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
|
||||||
|
|
||||||
use crate::AlwaysFixableViolation;
|
|
||||||
use crate::suppression::{InvalidSuppressionKind, ParseErrorKind};
|
|
||||||
|
|
||||||
/// ## What it does
|
|
||||||
/// Checks for invalid suppression comments
|
|
||||||
///
|
|
||||||
/// ## Why is this bad?
|
|
||||||
/// Invalid suppression comments are ignored by Ruff, and should either
|
|
||||||
/// be fixed or removed to avoid confusion.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
/// ```python
|
|
||||||
/// ruff: disable # missing codes
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Use instead:
|
|
||||||
/// ```python
|
|
||||||
/// # ruff: disable[E501]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Or delete the invalid suppression comment.
|
|
||||||
///
|
|
||||||
/// ## References
|
|
||||||
/// - [Ruff error suppression](https://docs.astral.sh/ruff/linter/#error-suppression)
|
|
||||||
#[derive(ViolationMetadata)]
|
|
||||||
#[violation_metadata(preview_since = "0.14.11")]
|
|
||||||
pub(crate) struct InvalidSuppressionComment {
|
|
||||||
pub(crate) kind: InvalidSuppressionCommentKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlwaysFixableViolation for InvalidSuppressionComment {
|
|
||||||
#[derive_message_formats]
|
|
||||||
fn message(&self) -> String {
|
|
||||||
let msg = match self.kind {
|
|
||||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Indentation) => {
|
|
||||||
"unexpected indentation".to_string()
|
|
||||||
}
|
|
||||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Trailing) => {
|
|
||||||
"trailing comments are not supported".to_string()
|
|
||||||
}
|
|
||||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Unmatched) => {
|
|
||||||
"no matching 'disable' comment".to_string()
|
|
||||||
}
|
|
||||||
InvalidSuppressionCommentKind::Error(error) => format!("{error}"),
|
|
||||||
};
|
|
||||||
format!("Invalid suppression comment: {msg}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fix_title(&self) -> String {
|
|
||||||
"Remove suppression comment".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum InvalidSuppressionCommentKind {
|
|
||||||
Invalid(InvalidSuppressionKind),
|
|
||||||
Error(ParseErrorKind),
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ pub(crate) use invalid_formatter_suppression_comment::*;
|
|||||||
pub(crate) use invalid_index_type::*;
|
pub(crate) use invalid_index_type::*;
|
||||||
pub(crate) use invalid_pyproject_toml::*;
|
pub(crate) use invalid_pyproject_toml::*;
|
||||||
pub(crate) use invalid_rule_code::*;
|
pub(crate) use invalid_rule_code::*;
|
||||||
pub(crate) use invalid_suppression_comment::*;
|
|
||||||
pub(crate) use legacy_form_pytest_raises::*;
|
pub(crate) use legacy_form_pytest_raises::*;
|
||||||
pub(crate) use logging_eager_conversion::*;
|
pub(crate) use logging_eager_conversion::*;
|
||||||
pub(crate) use map_int_version_parsing::*;
|
pub(crate) use map_int_version_parsing::*;
|
||||||
@@ -47,7 +46,6 @@ pub(crate) use starmap_zip::*;
|
|||||||
pub(crate) use static_key_dict_comprehension::*;
|
pub(crate) use static_key_dict_comprehension::*;
|
||||||
#[cfg(any(feature = "test-rules", test))]
|
#[cfg(any(feature = "test-rules", test))]
|
||||||
pub(crate) use test_rules::*;
|
pub(crate) use test_rules::*;
|
||||||
pub(crate) use unmatched_suppression_comment::*;
|
|
||||||
pub(crate) use unnecessary_cast_to_int::*;
|
pub(crate) use unnecessary_cast_to_int::*;
|
||||||
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||||
pub(crate) use unnecessary_key_check::*;
|
pub(crate) use unnecessary_key_check::*;
|
||||||
@@ -89,7 +87,6 @@ mod invalid_formatter_suppression_comment;
|
|||||||
mod invalid_index_type;
|
mod invalid_index_type;
|
||||||
mod invalid_pyproject_toml;
|
mod invalid_pyproject_toml;
|
||||||
mod invalid_rule_code;
|
mod invalid_rule_code;
|
||||||
mod invalid_suppression_comment;
|
|
||||||
mod legacy_form_pytest_raises;
|
mod legacy_form_pytest_raises;
|
||||||
mod logging_eager_conversion;
|
mod logging_eager_conversion;
|
||||||
mod map_int_version_parsing;
|
mod map_int_version_parsing;
|
||||||
@@ -116,7 +113,6 @@ mod static_key_dict_comprehension;
|
|||||||
mod suppression_comment_visitor;
|
mod suppression_comment_visitor;
|
||||||
#[cfg(any(feature = "test-rules", test))]
|
#[cfg(any(feature = "test-rules", test))]
|
||||||
pub(crate) mod test_rules;
|
pub(crate) mod test_rules;
|
||||||
mod unmatched_suppression_comment;
|
|
||||||
mod unnecessary_cast_to_int;
|
mod unnecessary_cast_to_int;
|
||||||
mod unnecessary_iterable_allocation_for_first_element;
|
mod unnecessary_iterable_allocation_for_first_element;
|
||||||
mod unnecessary_key_check;
|
mod unnecessary_key_check;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
|
||||||
|
|
||||||
use crate::Violation;
|
|
||||||
|
|
||||||
/// ## What it does
|
|
||||||
/// Checks for unmatched range suppression comments
|
|
||||||
///
|
|
||||||
/// ## Why is this bad?
|
|
||||||
/// Unmatched range suppression comments can inadvertently suppress violations
|
|
||||||
/// over larger sections of code than intended, particularly at module scope.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
/// ```python
|
|
||||||
/// def foo():
|
|
||||||
/// # ruff: disable[E501] # unmatched
|
|
||||||
/// REALLY_LONG_VALUES = [...]
|
|
||||||
///
|
|
||||||
/// print(REALLY_LONG_VALUES)
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Use instead:
|
|
||||||
/// ```python
|
|
||||||
/// def foo():
|
|
||||||
/// # ruff: disable[E501]
|
|
||||||
/// REALLY_LONG_VALUES = [...]
|
|
||||||
/// # ruff: enable[E501]
|
|
||||||
///
|
|
||||||
/// print(REALLY_LONG_VALUES)
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## References
|
|
||||||
/// - [Ruff error suppression](https://docs.astral.sh/ruff/linter/#error-suppression)
|
|
||||||
#[derive(ViolationMetadata)]
|
|
||||||
#[violation_metadata(preview_since = "0.14.11")]
|
|
||||||
pub(crate) struct UnmatchedSuppressionComment;
|
|
||||||
|
|
||||||
impl Violation for UnmatchedSuppressionComment {
|
|
||||||
#[derive_message_formats]
|
|
||||||
fn message(&self) -> String {
|
|
||||||
"Suppression comment without matching `#ruff:enable` comment".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,8 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs
|
|||||||
+linter.preview = enabled
|
+linter.preview = enabled
|
||||||
|
|
||||||
--- Summary ---
|
--- Summary ---
|
||||||
Removed: 15
|
Removed: 14
|
||||||
Added: 23
|
Added: 11
|
||||||
|
|
||||||
--- Removed ---
|
--- Removed ---
|
||||||
E741 Ambiguous variable name: `I`
|
E741 Ambiguous variable name: `I`
|
||||||
@@ -238,60 +238,8 @@ help: Remove assignment to unused variable `I`
|
|||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
|
||||||
F841 [*] Local variable `value` is assigned to but never used
|
|
||||||
--> suppressions.py:95:5
|
|
||||||
|
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
95 | value = 0
|
|
||||||
| ^^^^^
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
|
|
|
||||||
help: Remove assignment to unused variable `value`
|
|
||||||
92 | # Unknown rule codes
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
- value = 0
|
|
||||||
95 + pass
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
98 |
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Added ---
|
--- Added ---
|
||||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
|
||||||
--> suppressions.py:11:5
|
|
||||||
|
|
|
||||||
9 | # These should both be ignored by the implicit range suppression.
|
|
||||||
10 | # Should also generate an "unmatched suppression" warning.
|
|
||||||
11 | # ruff:disable[E741,F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
12 | I = 1
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
RUF103 [*] Invalid suppression comment: no matching 'disable' comment
|
|
||||||
--> suppressions.py:19:5
|
|
||||||
|
|
|
||||||
17 | # should be generated.
|
|
||||||
18 | I = 1
|
|
||||||
19 | # ruff: enable[E741, F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
help: Remove suppression comment
|
|
||||||
16 | # Neither warning is ignored, and an "unmatched suppression"
|
|
||||||
17 | # should be generated.
|
|
||||||
18 | I = 1
|
|
||||||
- # ruff: enable[E741, F841]
|
|
||||||
19 |
|
|
||||||
20 |
|
|
||||||
21 | def f():
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (non-enabled: `E501`)
|
RUF100 [*] Unused suppression (non-enabled: `E501`)
|
||||||
--> suppressions.py:46:5
|
--> suppressions.py:46:5
|
||||||
|
|
|
|
||||||
@@ -350,17 +298,6 @@ help: Remove unused `noqa` directive
|
|||||||
58 |
|
58 |
|
||||||
|
|
||||||
|
|
||||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
|
||||||
--> suppressions.py:61:5
|
|
||||||
|
|
|
||||||
59 | def f():
|
|
||||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
|
||||||
61 | # ruff: disable[F841, F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
62 | foo = 0
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (unused: `F841`)
|
RUF100 [*] Unused suppression (unused: `F841`)
|
||||||
--> suppressions.py:61:21
|
--> suppressions.py:61:21
|
||||||
|
|
|
|
||||||
@@ -381,18 +318,6 @@ help: Remove unused suppression
|
|||||||
64 |
|
64 |
|
||||||
|
|
||||||
|
|
||||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
|
||||||
--> suppressions.py:68:5
|
|
||||||
|
|
|
||||||
66 | # Overlapping range suppressions, one should be marked as used,
|
|
||||||
67 | # and the other should trigger an unused suppression diagnostic
|
|
||||||
68 | # ruff: disable[F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
69 | # ruff: disable[F841]
|
|
||||||
70 | foo = 0
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (unused: `F841`)
|
RUF100 [*] Unused suppression (unused: `F841`)
|
||||||
--> suppressions.py:69:5
|
--> suppressions.py:69:5
|
||||||
|
|
|
|
||||||
@@ -412,17 +337,6 @@ help: Remove unused suppression
|
|||||||
71 |
|
71 |
|
||||||
|
|
||||||
|
|
||||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
|
||||||
--> suppressions.py:75:5
|
|
||||||
|
|
|
||||||
73 | def f():
|
|
||||||
74 | # Multiple codes but only one is used
|
|
||||||
75 | # ruff: disable[E741, F401, F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
76 | foo = 0
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (unused: `E741`)
|
RUF100 [*] Unused suppression (unused: `E741`)
|
||||||
--> suppressions.py:75:21
|
--> suppressions.py:75:21
|
||||||
|
|
|
|
||||||
@@ -463,17 +377,6 @@ help: Remove unused suppression
|
|||||||
78 |
|
78 |
|
||||||
|
|
||||||
|
|
||||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
|
||||||
--> suppressions.py:81:5
|
|
||||||
|
|
|
||||||
79 | def f():
|
|
||||||
80 | # Multiple codes but only two are used
|
|
||||||
81 | # ruff: disable[E741, F401, F841]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
82 | I = 0
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||||
--> suppressions.py:81:27
|
--> suppressions.py:81:27
|
||||||
|
|
|
|
||||||
@@ -510,8 +413,6 @@ help: Remove unused suppression
|
|||||||
- # ruff: disable[E741, F401, F841]
|
- # ruff: disable[E741, F401, F841]
|
||||||
87 + # ruff: disable[F401, F841]
|
87 + # ruff: disable[F401, F841]
|
||||||
88 | print("hello")
|
88 | print("hello")
|
||||||
89 |
|
|
||||||
90 |
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||||
@@ -530,8 +431,6 @@ help: Remove unused suppression
|
|||||||
- # ruff: disable[E741, F401, F841]
|
- # ruff: disable[E741, F401, F841]
|
||||||
87 + # ruff: disable[E741, F841]
|
87 + # ruff: disable[E741, F841]
|
||||||
88 | print("hello")
|
88 | print("hello")
|
||||||
89 |
|
|
||||||
90 |
|
|
||||||
|
|
||||||
|
|
||||||
RUF100 [*] Unused suppression (unused: `F841`)
|
RUF100 [*] Unused suppression (unused: `F841`)
|
||||||
@@ -550,122 +449,3 @@ help: Remove unused suppression
|
|||||||
- # ruff: disable[E741, F401, F841]
|
- # ruff: disable[E741, F401, F841]
|
||||||
87 + # ruff: disable[E741, F401]
|
87 + # ruff: disable[E741, F401]
|
||||||
88 | print("hello")
|
88 | print("hello")
|
||||||
89 |
|
|
||||||
90 |
|
|
||||||
|
|
||||||
|
|
||||||
RUF102 [*] Invalid rule code in suppression: YF829
|
|
||||||
--> suppressions.py:93:21
|
|
||||||
|
|
|
||||||
91 | def f():
|
|
||||||
92 | # Unknown rule codes
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
| ^^^^^
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
95 | value = 0
|
|
||||||
|
|
|
||||||
help: Remove the rule code
|
|
||||||
90 |
|
|
||||||
91 | def f():
|
|
||||||
92 | # Unknown rule codes
|
|
||||||
- # ruff: disable[YF829]
|
|
||||||
93 | # ruff: disable[F841, RQW320]
|
|
||||||
94 | value = 0
|
|
||||||
95 | # ruff: enable[F841, RQW320]
|
|
||||||
|
|
||||||
|
|
||||||
RUF102 [*] Invalid rule code in suppression: RQW320
|
|
||||||
--> suppressions.py:94:27
|
|
||||||
|
|
|
||||||
92 | # Unknown rule codes
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
| ^^^^^^
|
|
||||||
95 | value = 0
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
|
|
|
||||||
help: Remove the rule code
|
|
||||||
91 | def f():
|
|
||||||
92 | # Unknown rule codes
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
- # ruff: disable[F841, RQW320]
|
|
||||||
94 + # ruff: disable[F841]
|
|
||||||
95 | value = 0
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
|
|
||||||
|
|
||||||
RUF102 [*] Invalid rule code in suppression: RQW320
|
|
||||||
--> suppressions.py:96:26
|
|
||||||
|
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
95 | value = 0
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
| ^^^^^^
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
|
|
|
||||||
help: Remove the rule code
|
|
||||||
93 | # ruff: disable[YF829]
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
95 | value = 0
|
|
||||||
- # ruff: enable[F841, RQW320]
|
|
||||||
96 + # ruff: enable[F841]
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
98 |
|
|
||||||
99 |
|
|
||||||
|
|
||||||
|
|
||||||
RUF102 [*] Invalid rule code in suppression: YF829
|
|
||||||
--> suppressions.py:97:20
|
|
||||||
|
|
|
||||||
95 | value = 0
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
97 | # ruff: enable[YF829]
|
|
||||||
| ^^^^^
|
|
||||||
|
|
|
||||||
help: Remove the rule code
|
|
||||||
94 | # ruff: disable[F841, RQW320]
|
|
||||||
95 | value = 0
|
|
||||||
96 | # ruff: enable[F841, RQW320]
|
|
||||||
- # ruff: enable[YF829]
|
|
||||||
97 |
|
|
||||||
98 |
|
|
||||||
99 | def f():
|
|
||||||
|
|
||||||
|
|
||||||
RUF103 [*] Invalid suppression comment: missing suppression codes like `[E501, ...]`
|
|
||||||
--> suppressions.py:109:5
|
|
||||||
|
|
|
||||||
107 | def f():
|
|
||||||
108 | # Empty or missing rule codes
|
|
||||||
109 | # ruff: disable
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
110 | # ruff: disable[]
|
|
||||||
111 | print("hello")
|
|
||||||
|
|
|
||||||
help: Remove suppression comment
|
|
||||||
106 |
|
|
||||||
107 | def f():
|
|
||||||
108 | # Empty or missing rule codes
|
|
||||||
- # ruff: disable
|
|
||||||
109 | # ruff: disable[]
|
|
||||||
110 | print("hello")
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|
||||||
|
|
||||||
RUF103 [*] Invalid suppression comment: missing suppression codes like `[E501, ...]`
|
|
||||||
--> suppressions.py:110:5
|
|
||||||
|
|
|
||||||
108 | # Empty or missing rule codes
|
|
||||||
109 | # ruff: disable
|
|
||||||
110 | # ruff: disable[]
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
111 | print("hello")
|
|
||||||
|
|
|
||||||
help: Remove suppression comment
|
|
||||||
107 | def f():
|
|
||||||
108 | # Empty or missing rule codes
|
|
||||||
109 | # ruff: disable
|
|
||||||
- # ruff: disable[]
|
|
||||||
110 | print("hello")
|
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
|
||||||
|
|||||||
@@ -471,13 +471,6 @@ impl LinterSettings {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn with_external_rules(mut self, rules: &[&str]) -> Self {
|
|
||||||
self.external
|
|
||||||
.extend(rules.iter().map(std::string::ToString::to_string));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve the [`TargetVersion`] to use for linting.
|
/// Resolve the [`TargetVersion`] to use for linting.
|
||||||
///
|
///
|
||||||
/// This method respects the per-file version overrides in
|
/// This method respects the per-file version overrides in
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/ruff_linter/src/linter.rs
|
|
||||||
---
|
|
||||||
invalid-syntax: annotated name `a` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:4:5
|
|
||||||
|
|
|
||||||
2 | def f1():
|
|
||||||
3 | global a
|
|
||||||
4 | a: str = "foo" # error
|
|
||||||
| ^
|
|
||||||
5 |
|
|
||||||
6 | b: int = 1
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `b` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:10:9
|
|
||||||
|
|
|
||||||
8 | def inner():
|
|
||||||
9 | global b
|
|
||||||
10 | b: str = "nested" # error
|
|
||||||
| ^
|
|
||||||
11 |
|
|
||||||
12 | c: int = 1
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `c` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:15:5
|
|
||||||
|
|
|
||||||
13 | def f2():
|
|
||||||
14 | global c
|
|
||||||
15 | c: list[str] = [] # error
|
|
||||||
| ^
|
|
||||||
16 |
|
|
||||||
17 | d: int = 1
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `d` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:20:5
|
|
||||||
|
|
|
||||||
18 | def f3():
|
|
||||||
19 | global d
|
|
||||||
20 | d: str # error
|
|
||||||
| ^
|
|
||||||
21 |
|
|
||||||
22 | e: int = 1
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `g` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:29:1
|
|
||||||
|
|
|
||||||
27 | f: int = 1 # okay
|
|
||||||
28 |
|
|
||||||
29 | g: int = 1
|
|
||||||
| ^
|
|
||||||
30 | global g # error
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `x` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:33:5
|
|
||||||
|
|
|
||||||
32 | class C:
|
|
||||||
33 | x: str
|
|
||||||
| ^
|
|
||||||
34 | global x # error
|
|
||||||
|
|
|
||||||
|
|
||||||
invalid-syntax: annotated name `x` can't be global
|
|
||||||
--> resources/test/fixtures/semantic_errors/annotated_global.py:38:5
|
|
||||||
|
|
|
||||||
36 | class D:
|
|
||||||
37 | global x # error
|
|
||||||
38 | x: str
|
|
||||||
| ^
|
|
||||||
|
|
|
||||||
@@ -4,7 +4,6 @@ use ruff_db::diagnostic::Diagnostic;
|
|||||||
use ruff_diagnostics::{Edit, Fix};
|
use ruff_diagnostics::{Edit, Fix};
|
||||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||||
use ruff_python_ast::whitespace::indentation;
|
use ruff_python_ast::whitespace::indentation;
|
||||||
use rustc_hash::FxHashSet;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::{error::Error, fmt::Formatter};
|
use std::{error::Error, fmt::Formatter};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -18,11 +17,7 @@ use crate::checkers::ast::LintContext;
|
|||||||
use crate::codes::Rule;
|
use crate::codes::Rule;
|
||||||
use crate::fix::edits::delete_comment;
|
use crate::fix::edits::delete_comment;
|
||||||
use crate::preview::is_range_suppressions_enabled;
|
use crate::preview::is_range_suppressions_enabled;
|
||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA, UnusedNOQAKind};
|
||||||
use crate::rules::ruff::rules::{
|
|
||||||
InvalidRuleCode, InvalidRuleCodeKind, InvalidSuppressionComment, InvalidSuppressionCommentKind,
|
|
||||||
UnmatchedSuppressionComment, UnusedCodes, UnusedNOQA, UnusedNOQAKind, code_is_valid,
|
|
||||||
};
|
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -135,7 +130,7 @@ impl Suppressions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_empty(&self) -> bool {
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
self.valid.is_empty() && self.invalid.is_empty() && self.errors.is_empty()
|
self.valid.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a diagnostic is suppressed by any known range suppressions
|
/// Check if a diagnostic is suppressed by any known range suppressions
|
||||||
@@ -155,9 +150,7 @@ impl Suppressions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for suppression in &self.valid {
|
for suppression in &self.valid {
|
||||||
let suppression_code =
|
if *code == suppression.code.as_str() && suppression.range.contains_range(range) {
|
||||||
get_redirect_target(suppression.code.as_str()).unwrap_or(suppression.code.as_str());
|
|
||||||
if *code == suppression_code && suppression.range.contains_range(range) {
|
|
||||||
suppression.used.set(true);
|
suppression.used.set(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -166,140 +159,81 @@ impl Suppressions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_suppressions(&self, context: &LintContext, locator: &Locator) {
|
pub(crate) fn check_suppressions(&self, context: &LintContext, locator: &Locator) {
|
||||||
let mut unmatched_ranges = FxHashSet::default();
|
if !context.any_rule_enabled(&[Rule::UnusedNOQA, Rule::InvalidRuleCode]) {
|
||||||
for suppression in &self.valid {
|
return;
|
||||||
if !code_is_valid(&suppression.code, &context.settings().external) {
|
|
||||||
// InvalidRuleCode
|
|
||||||
if context.is_rule_enabled(Rule::InvalidRuleCode) {
|
|
||||||
for comment in &suppression.comments {
|
|
||||||
let (range, edit) = Suppressions::delete_code_or_comment(
|
|
||||||
locator,
|
|
||||||
suppression,
|
|
||||||
comment,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.report_diagnostic(
|
|
||||||
InvalidRuleCode {
|
|
||||||
rule_code: suppression.code.to_string(),
|
|
||||||
kind: InvalidRuleCodeKind::Suppression,
|
|
||||||
},
|
|
||||||
range,
|
|
||||||
)
|
|
||||||
.set_fix(Fix::safe_edit(edit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !suppression.used.get() {
|
|
||||||
// UnusedNOQA
|
|
||||||
if context.is_rule_enabled(Rule::UnusedNOQA) {
|
|
||||||
let Ok(rule) = Rule::from_code(
|
|
||||||
get_redirect_target(&suppression.code).unwrap_or(&suppression.code),
|
|
||||||
) else {
|
|
||||||
continue; // "external" lint code, don't treat it as unused
|
|
||||||
};
|
|
||||||
for comment in &suppression.comments {
|
|
||||||
let (range, edit) = Suppressions::delete_code_or_comment(
|
|
||||||
locator,
|
|
||||||
suppression,
|
|
||||||
comment,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let codes = if context.is_rule_enabled(rule) {
|
|
||||||
UnusedCodes {
|
|
||||||
unmatched: vec![suppression.code.to_string()],
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UnusedCodes {
|
|
||||||
disabled: vec![suppression.code.to_string()],
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
context
|
|
||||||
.report_diagnostic(
|
|
||||||
UnusedNOQA {
|
|
||||||
codes: Some(codes),
|
|
||||||
kind: UnusedNOQAKind::Suppression,
|
|
||||||
},
|
|
||||||
range,
|
|
||||||
)
|
|
||||||
.set_fix(Fix::safe_edit(edit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if suppression.comments.len() == 1 {
|
|
||||||
// UnmatchedSuppressionComment
|
|
||||||
let range = suppression.comments[0].range;
|
|
||||||
if unmatched_ranges.insert(range) {
|
|
||||||
context.report_diagnostic_if_enabled(UnmatchedSuppressionComment {}, range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.is_rule_enabled(Rule::InvalidSuppressionComment) {
|
let unused = self
|
||||||
for error in &self.errors {
|
.valid
|
||||||
context
|
.iter()
|
||||||
.report_diagnostic(
|
.filter(|suppression| !suppression.used.get());
|
||||||
InvalidSuppressionComment {
|
|
||||||
kind: InvalidSuppressionCommentKind::Error(error.kind),
|
|
||||||
},
|
|
||||||
error.range,
|
|
||||||
)
|
|
||||||
.set_fix(Fix::unsafe_edit(delete_comment(error.range, locator)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.is_rule_enabled(Rule::InvalidSuppressionComment) {
|
for suppression in unused {
|
||||||
for invalid in &self.invalid {
|
let Ok(rule) = Rule::from_code(&suppression.code) else {
|
||||||
context
|
continue; // TODO: invalid code
|
||||||
.report_diagnostic(
|
|
||||||
InvalidSuppressionComment {
|
|
||||||
kind: InvalidSuppressionCommentKind::Invalid(invalid.kind),
|
|
||||||
},
|
|
||||||
invalid.comment.range,
|
|
||||||
)
|
|
||||||
.set_fix(Fix::unsafe_edit(delete_comment(
|
|
||||||
invalid.comment.range,
|
|
||||||
locator,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_code_or_comment(
|
|
||||||
locator: &Locator<'_>,
|
|
||||||
suppression: &Suppression,
|
|
||||||
comment: &SuppressionComment,
|
|
||||||
highlight_only_code: bool,
|
|
||||||
) -> (TextRange, Edit) {
|
|
||||||
let mut range = comment.range;
|
|
||||||
let edit = if comment.codes.len() == 1 {
|
|
||||||
if highlight_only_code {
|
|
||||||
range = comment.codes[0];
|
|
||||||
}
|
|
||||||
delete_comment(comment.range, locator)
|
|
||||||
} else {
|
|
||||||
let code_index = comment
|
|
||||||
.codes
|
|
||||||
.iter()
|
|
||||||
.position(|range| locator.slice(range) == suppression.code)
|
|
||||||
.unwrap();
|
|
||||||
range = comment.codes[code_index];
|
|
||||||
let code_range = if code_index < (comment.codes.len() - 1) {
|
|
||||||
TextRange::new(
|
|
||||||
comment.codes[code_index].start(),
|
|
||||||
comment.codes[code_index + 1].start(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
TextRange::new(
|
|
||||||
comment.codes[code_index - 1].end(),
|
|
||||||
comment.codes[code_index].end(),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
Edit::range_deletion(code_range)
|
for comment in &suppression.comments {
|
||||||
};
|
let mut range = comment.range;
|
||||||
(range, edit)
|
let edit = if comment.codes.len() == 1 {
|
||||||
|
delete_comment(comment.range, locator)
|
||||||
|
} else {
|
||||||
|
let code_index = comment
|
||||||
|
.codes
|
||||||
|
.iter()
|
||||||
|
.position(|range| locator.slice(range) == suppression.code)
|
||||||
|
.unwrap();
|
||||||
|
range = comment.codes[code_index];
|
||||||
|
let code_range = if code_index < (comment.codes.len() - 1) {
|
||||||
|
TextRange::new(
|
||||||
|
comment.codes[code_index].start(),
|
||||||
|
comment.codes[code_index + 1].start(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TextRange::new(
|
||||||
|
comment.codes[code_index - 1].end(),
|
||||||
|
comment.codes[code_index].end(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Edit::range_deletion(code_range)
|
||||||
|
};
|
||||||
|
|
||||||
|
let codes = if context.is_rule_enabled(rule) {
|
||||||
|
UnusedCodes {
|
||||||
|
unmatched: vec![suppression.code.to_string()],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UnusedCodes {
|
||||||
|
disabled: vec![suppression.code.to_string()],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic = context.report_diagnostic(
|
||||||
|
UnusedNOQA {
|
||||||
|
codes: Some(codes),
|
||||||
|
kind: UnusedNOQAKind::Suppression,
|
||||||
|
},
|
||||||
|
range,
|
||||||
|
);
|
||||||
|
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for error in self
|
||||||
|
.errors
|
||||||
|
.iter()
|
||||||
|
.filter(|error| error.kind == ParseErrorKind::MissingCodes)
|
||||||
|
{
|
||||||
|
let mut diagnostic = context.report_diagnostic(
|
||||||
|
UnusedNOQA {
|
||||||
|
codes: Some(UnusedCodes::default()),
|
||||||
|
kind: UnusedNOQAKind::Suppression,
|
||||||
|
},
|
||||||
|
error.range,
|
||||||
|
);
|
||||||
|
diagnostic.set_fix(Fix::safe_edit(delete_comment(error.range, locator)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +391,7 @@ impl<'a> SuppressionsBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
|
||||||
pub(crate) enum ParseErrorKind {
|
enum ParseErrorKind {
|
||||||
#[error("not a suppression comment")]
|
#[error("not a suppression comment")]
|
||||||
NotASuppression,
|
NotASuppression,
|
||||||
|
|
||||||
@@ -467,7 +401,7 @@ pub(crate) enum ParseErrorKind {
|
|||||||
#[error("unknown ruff directive")]
|
#[error("unknown ruff directive")]
|
||||||
UnknownAction,
|
UnknownAction,
|
||||||
|
|
||||||
#[error("missing suppression codes like `[E501, ...]`")]
|
#[error("missing suppression codes")]
|
||||||
MissingCodes,
|
MissingCodes,
|
||||||
|
|
||||||
#[error("missing closing bracket")]
|
#[error("missing closing bracket")]
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ pub use python_version::*;
|
|||||||
pub mod comparable;
|
pub mod comparable;
|
||||||
pub mod docstrings;
|
pub mod docstrings;
|
||||||
mod expression;
|
mod expression;
|
||||||
pub mod find_node;
|
|
||||||
mod generated;
|
mod generated;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
|
|||||||
@@ -2,25 +2,18 @@
|
|||||||
use crate::{self as ast, AnyNodeRef, ExceptHandler, Stmt};
|
use crate::{self as ast, AnyNodeRef, ExceptHandler, Stmt};
|
||||||
|
|
||||||
/// Given a [`Stmt`] and its parent, return the [`ast::Suite`] that contains the [`Stmt`].
|
/// Given a [`Stmt`] and its parent, return the [`ast::Suite`] that contains the [`Stmt`].
|
||||||
pub fn suite<'a>(
|
pub fn suite<'a>(stmt: &'a Stmt, parent: &'a Stmt) -> Option<EnclosingSuite<'a>> {
|
||||||
stmt: impl Into<AnyNodeRef<'a>>,
|
|
||||||
parent: impl Into<AnyNodeRef<'a>>,
|
|
||||||
) -> Option<EnclosingSuite<'a>> {
|
|
||||||
// TODO: refactor this to work without a parent, ie when `stmt` is at the top level
|
// TODO: refactor this to work without a parent, ie when `stmt` is at the top level
|
||||||
let stmt = stmt.into();
|
match parent {
|
||||||
match parent.into() {
|
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. }) => EnclosingSuite::new(body, stmt),
|
||||||
AnyNodeRef::ModModule(ast::ModModule { body, .. }) => EnclosingSuite::new(body, stmt),
|
Stmt::ClassDef(ast::StmtClassDef { body, .. }) => EnclosingSuite::new(body, stmt),
|
||||||
AnyNodeRef::StmtFunctionDef(ast::StmtFunctionDef { body, .. }) => {
|
Stmt::For(ast::StmtFor { body, orelse, .. }) => [body, orelse]
|
||||||
EnclosingSuite::new(body, stmt)
|
|
||||||
}
|
|
||||||
AnyNodeRef::StmtClassDef(ast::StmtClassDef { body, .. }) => EnclosingSuite::new(body, stmt),
|
|
||||||
AnyNodeRef::StmtFor(ast::StmtFor { body, orelse, .. }) => [body, orelse]
|
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
||||||
AnyNodeRef::StmtWhile(ast::StmtWhile { body, orelse, .. }) => [body, orelse]
|
Stmt::While(ast::StmtWhile { body, orelse, .. }) => [body, orelse]
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
||||||
AnyNodeRef::StmtIf(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
body,
|
body,
|
||||||
elif_else_clauses,
|
elif_else_clauses,
|
||||||
..
|
..
|
||||||
@@ -28,12 +21,12 @@ pub fn suite<'a>(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
|
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
|
||||||
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
.find_map(|suite| EnclosingSuite::new(suite, stmt)),
|
||||||
AnyNodeRef::StmtWith(ast::StmtWith { body, .. }) => EnclosingSuite::new(body, stmt),
|
Stmt::With(ast::StmtWith { body, .. }) => EnclosingSuite::new(body, stmt),
|
||||||
AnyNodeRef::StmtMatch(ast::StmtMatch { cases, .. }) => cases
|
Stmt::Match(ast::StmtMatch { cases, .. }) => cases
|
||||||
.iter()
|
.iter()
|
||||||
.map(|case| &case.body)
|
.map(|case| &case.body)
|
||||||
.find_map(|body| EnclosingSuite::new(body, stmt)),
|
.find_map(|body| EnclosingSuite::new(body, stmt)),
|
||||||
AnyNodeRef::StmtTry(ast::StmtTry {
|
Stmt::Try(ast::StmtTry {
|
||||||
body,
|
body,
|
||||||
handlers,
|
handlers,
|
||||||
orelse,
|
orelse,
|
||||||
@@ -58,10 +51,10 @@ pub struct EnclosingSuite<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EnclosingSuite<'a> {
|
impl<'a> EnclosingSuite<'a> {
|
||||||
pub fn new(suite: &'a [Stmt], stmt: AnyNodeRef<'a>) -> Option<Self> {
|
pub fn new(suite: &'a [Stmt], stmt: &'a Stmt) -> Option<Self> {
|
||||||
let position = suite
|
let position = suite
|
||||||
.iter()
|
.iter()
|
||||||
.position(|sibling| AnyNodeRef::ptr_eq(sibling.into(), stmt))?;
|
.position(|sibling| AnyNodeRef::ptr_eq(sibling.into(), stmt.into()))?;
|
||||||
|
|
||||||
Some(EnclosingSuite { suite, position })
|
Some(EnclosingSuite { suite, position })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,17 +222,6 @@ where
|
|||||||
visitor.leave_node(node);
|
visitor.leave_node(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_node<'a, V>(visitor: &mut V, node: AnyNodeRef<'a>)
|
|
||||||
where
|
|
||||||
V: SourceOrderVisitor<'a> + ?Sized,
|
|
||||||
{
|
|
||||||
if visitor.enter_node(node).is_traverse() {
|
|
||||||
node.visit_source_order(visitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitor.leave_node(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
pub enum TraversalSignal {
|
pub enum TraversalSignal {
|
||||||
Traverse,
|
Traverse,
|
||||||
|
|||||||
@@ -592,23 +592,11 @@ impl FormatString {
|
|||||||
fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> {
|
fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> {
|
||||||
let mut cur_text = text;
|
let mut cur_text = text;
|
||||||
let mut result_string = String::new();
|
let mut result_string = String::new();
|
||||||
let mut pending_escape = false;
|
|
||||||
while !cur_text.is_empty() {
|
while !cur_text.is_empty() {
|
||||||
if pending_escape
|
|
||||||
&& let Some((unicode_string, remaining)) =
|
|
||||||
FormatString::parse_escaped_unicode_string(cur_text)
|
|
||||||
{
|
|
||||||
result_string.push_str(unicode_string);
|
|
||||||
cur_text = remaining;
|
|
||||||
pending_escape = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match FormatString::parse_literal_single(cur_text) {
|
match FormatString::parse_literal_single(cur_text) {
|
||||||
Ok((next_char, remaining)) => {
|
Ok((next_char, remaining)) => {
|
||||||
result_string.push(next_char);
|
result_string.push(next_char);
|
||||||
cur_text = remaining;
|
cur_text = remaining;
|
||||||
pending_escape = next_char == '\\' && !pending_escape;
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return if result_string.is_empty() {
|
return if result_string.is_empty() {
|
||||||
@@ -690,13 +678,6 @@ impl FormatString {
|
|||||||
}
|
}
|
||||||
Err(FormatParseError::UnmatchedBracket)
|
Err(FormatParseError::UnmatchedBracket)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_escaped_unicode_string(text: &str) -> Option<(&str, &str)> {
|
|
||||||
text.strip_prefix("N{")?.find('}').map(|idx| {
|
|
||||||
let end_idx = idx + 3; // 3 for "N{"
|
|
||||||
(&text[..end_idx], &text[end_idx..])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromTemplate<'a>: Sized {
|
pub trait FromTemplate<'a>: Sized {
|
||||||
@@ -1039,48 +1020,4 @@ mod tests {
|
|||||||
Err(FormatParseError::InvalidCharacterAfterRightBracket)
|
Err(FormatParseError::InvalidCharacterAfterRightBracket)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_format_unicode_escape() {
|
|
||||||
let expected = Ok(FormatString {
|
|
||||||
format_parts: vec![FormatPart::Literal("I am a \\N{snowman}".to_owned())],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(FormatString::from_str("I am a \\N{snowman}"), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_format_unicode_escape_with_field() {
|
|
||||||
let expected = Ok(FormatString {
|
|
||||||
format_parts: vec![
|
|
||||||
FormatPart::Literal("I am a \\N{snowman}".to_owned()),
|
|
||||||
FormatPart::Field {
|
|
||||||
field_name: "snowman".to_owned(),
|
|
||||||
conversion_spec: None,
|
|
||||||
format_spec: String::new(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
FormatString::from_str("I am a \\N{snowman}{snowman}"),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_format_multiple_escape_with_field() {
|
|
||||||
let expected = Ok(FormatString {
|
|
||||||
format_parts: vec![
|
|
||||||
FormatPart::Literal("I am a \\\\N".to_owned()),
|
|
||||||
FormatPart::Field {
|
|
||||||
field_name: "snowman".to_owned(),
|
|
||||||
conversion_spec: None,
|
|
||||||
format_spec: String::new(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(FormatString::from_str("I am a \\\\N{snowman}"), expected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,9 +272,7 @@ impl SemanticSyntaxChecker {
|
|||||||
|
|
||||||
fn check_annotation<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
|
fn check_annotation<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. }) => {
|
||||||
target, annotation, ..
|
|
||||||
}) => {
|
|
||||||
if ctx.python_version() > PythonVersion::PY313 {
|
if ctx.python_version() > PythonVersion::PY313 {
|
||||||
// test_ok valid_annotation_py313
|
// test_ok valid_annotation_py313
|
||||||
// # parse_options: {"target-version": "3.13"}
|
// # parse_options: {"target-version": "3.13"}
|
||||||
@@ -299,18 +297,6 @@ impl SemanticSyntaxChecker {
|
|||||||
};
|
};
|
||||||
visitor.visit_expr(annotation);
|
visitor.visit_expr(annotation);
|
||||||
}
|
}
|
||||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
|
||||||
if let Some(global_stmt) = ctx.global(id.as_str()) {
|
|
||||||
let global_start = global_stmt.start();
|
|
||||||
if !ctx.in_module_scope() || target.start() < global_start {
|
|
||||||
Self::add_error(
|
|
||||||
ctx,
|
|
||||||
SemanticSyntaxErrorKind::AnnotatedGlobal(id.to_string()),
|
|
||||||
target.range(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
type_params,
|
type_params,
|
||||||
|
|||||||
@@ -179,45 +179,42 @@ impl LineIndex {
|
|||||||
let line = self.line_index(offset);
|
let line = self.line_index(offset);
|
||||||
let line_start = self.line_start(line, text);
|
let line_start = self.line_start(line, text);
|
||||||
|
|
||||||
let character_offset =
|
|
||||||
self.characters_between(TextRange::new(line_start, offset), text, encoding);
|
|
||||||
|
|
||||||
SourceLocation {
|
|
||||||
line,
|
|
||||||
character_offset: OneIndexed::from_zero_indexed(character_offset),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn characters_between(
|
|
||||||
&self,
|
|
||||||
range: TextRange,
|
|
||||||
text: &str,
|
|
||||||
encoding: PositionEncoding,
|
|
||||||
) -> usize {
|
|
||||||
if self.is_ascii() {
|
if self.is_ascii() {
|
||||||
return (range.end() - range.start()).to_usize();
|
return SourceLocation {
|
||||||
|
line,
|
||||||
|
character_offset: OneIndexed::from_zero_indexed((offset - line_start).to_usize()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match encoding {
|
match encoding {
|
||||||
PositionEncoding::Utf8 => (range.end() - range.start()).to_usize(),
|
PositionEncoding::Utf8 => {
|
||||||
|
let character_offset = offset - line_start;
|
||||||
|
SourceLocation {
|
||||||
|
line,
|
||||||
|
character_offset: OneIndexed::from_zero_indexed(character_offset.to_usize()),
|
||||||
|
}
|
||||||
|
}
|
||||||
PositionEncoding::Utf16 => {
|
PositionEncoding::Utf16 => {
|
||||||
let up_to_character = &text[range];
|
let up_to_character = &text[TextRange::new(line_start, offset)];
|
||||||
up_to_character.encode_utf16().count()
|
let character = up_to_character.encode_utf16().count();
|
||||||
|
|
||||||
|
SourceLocation {
|
||||||
|
line,
|
||||||
|
character_offset: OneIndexed::from_zero_indexed(character),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PositionEncoding::Utf32 => {
|
PositionEncoding::Utf32 => {
|
||||||
let up_to_character = &text[range];
|
let up_to_character = &text[TextRange::new(line_start, offset)];
|
||||||
up_to_character.chars().count()
|
let character = up_to_character.chars().count();
|
||||||
|
|
||||||
|
SourceLocation {
|
||||||
|
line,
|
||||||
|
character_offset: OneIndexed::from_zero_indexed(character),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the length of the line in characters, respecting the given encoding
|
|
||||||
pub fn line_len(&self, line: OneIndexed, text: &str, encoding: PositionEncoding) -> usize {
|
|
||||||
let line_range = self.line_range(line, text);
|
|
||||||
|
|
||||||
self.characters_between(line_range, text, encoding)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the number of lines in the source code.
|
/// Return the number of lines in the source code.
|
||||||
pub fn line_count(&self) -> usize {
|
pub fn line_count(&self) -> usize {
|
||||||
self.line_starts().len()
|
self.line_starts().len()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_wasm"
|
name = "ruff_wasm"
|
||||||
version = "0.14.10"
|
version = "0.14.9"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|||||||
1
crates/ty/docs/cli.md
generated
1
crates/ty/docs/cli.md
generated
@@ -56,7 +56,6 @@ over all configuration files.</p>
|
|||||||
</dd><dt id="ty-check--exit-zero"><a href="#ty-check--exit-zero"><code>--exit-zero</code></a></dt><dd><p>Always use exit code 0, even when there are error-level diagnostics</p>
|
</dd><dt id="ty-check--exit-zero"><a href="#ty-check--exit-zero"><code>--exit-zero</code></a></dt><dd><p>Always use exit code 0, even when there are error-level diagnostics</p>
|
||||||
</dd><dt id="ty-check--extra-search-path"><a href="#ty-check--extra-search-path"><code>--extra-search-path</code></a> <i>path</i></dt><dd><p>Additional path to use as a module-resolution source (can be passed multiple times).</p>
|
</dd><dt id="ty-check--extra-search-path"><a href="#ty-check--extra-search-path"><code>--extra-search-path</code></a> <i>path</i></dt><dd><p>Additional path to use as a module-resolution source (can be passed multiple times).</p>
|
||||||
<p>This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use <code>--python</code> to point ty to your Python environment if it is in an unusual location.</p>
|
<p>This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use <code>--python</code> to point ty to your Python environment if it is in an unusual location.</p>
|
||||||
</dd><dt id="ty-check--force-exclude"><a href="#ty-check--force-exclude"><code>--force-exclude</code></a></dt><dd><p>Enforce exclusions, even for paths passed to ty directly on the command-line. Use <code>--no-force-exclude</code> to disable</p>
|
|
||||||
</dd><dt id="ty-check--help"><a href="#ty-check--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
|
</dd><dt id="ty-check--help"><a href="#ty-check--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
|
||||||
</dd><dt id="ty-check--ignore"><a href="#ty-check--ignore"><code>--ignore</code></a> <i>rule</i></dt><dd><p>Disables the rule. Can be specified multiple times.</p>
|
</dd><dt id="ty-check--ignore"><a href="#ty-check--ignore"><code>--ignore</code></a> <i>rule</i></dt><dd><p>Disables the rule. Can be specified multiple times.</p>
|
||||||
</dd><dt id="ty-check--no-progress"><a href="#ty-check--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
|
</dd><dt id="ty-check--no-progress"><a href="#ty-check--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
|
||||||
|
|||||||
16
crates/ty/docs/configuration.md
generated
16
crates/ty/docs/configuration.md
generated
@@ -200,22 +200,24 @@ Configuration override that applies to specific files based on glob patterns.
|
|||||||
|
|
||||||
An override allows you to apply different rule configurations to specific
|
An override allows you to apply different rule configurations to specific
|
||||||
files or directories. Multiple overrides can match the same file, with
|
files or directories. Multiple overrides can match the same file, with
|
||||||
later overrides take precedence. Override rules take precedence over global
|
later overrides take precedence.
|
||||||
rules for matching files.
|
|
||||||
|
|
||||||
For example, to relax enforcement of rules in test files:
|
### Precedence
|
||||||
|
|
||||||
|
- Later overrides in the array take precedence over earlier ones
|
||||||
|
- Override rules take precedence over global rules for matching files
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
# Relax rules for test files
|
||||||
[[tool.ty.overrides]]
|
[[tool.ty.overrides]]
|
||||||
include = ["tests/**", "**/test_*.py"]
|
include = ["tests/**", "**/test_*.py"]
|
||||||
|
|
||||||
[tool.ty.overrides.rules]
|
[tool.ty.overrides.rules]
|
||||||
possibly-unresolved-reference = "warn"
|
possibly-unresolved-reference = "warn"
|
||||||
```
|
|
||||||
|
|
||||||
Or, to ignore a rule in generated files but retain enforcement in an important file:
|
# Ignore generated files but still check important ones
|
||||||
|
|
||||||
```toml
|
|
||||||
[[tool.ty.overrides]]
|
[[tool.ty.overrides]]
|
||||||
include = ["generated/**"]
|
include = ["generated/**"]
|
||||||
exclude = ["generated/important.py"]
|
exclude = ["generated/important.py"]
|
||||||
|
|||||||
@@ -2,15 +2,6 @@
|
|||||||
|
|
||||||
ty defines and respects the following environment variables:
|
ty defines and respects the following environment variables:
|
||||||
|
|
||||||
### `TY_CONFIG_FILE`
|
|
||||||
|
|
||||||
Path to a `ty.toml` configuration file to use.
|
|
||||||
|
|
||||||
When set, ty will use this file for configuration instead of
|
|
||||||
discovering configuration files automatically.
|
|
||||||
|
|
||||||
Equivalent to the `--config-file` command-line argument.
|
|
||||||
|
|
||||||
### `TY_LOG`
|
### `TY_LOG`
|
||||||
|
|
||||||
If set, ty will use this value as the log level for its `--verbose` output.
|
If set, ty will use this value as the log level for its `--verbose` output.
|
||||||
|
|||||||
1217
crates/ty/docs/rules.md
generated
1217
crates/ty/docs/rules.md
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ use ty_combine::Combine;
|
|||||||
use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions};
|
use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions};
|
||||||
use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource};
|
use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource};
|
||||||
use ty_python_semantic::lint;
|
use ty_python_semantic::lint;
|
||||||
use ty_static::EnvVars;
|
|
||||||
|
|
||||||
// Configures Clap v3-style help menu colors
|
// Configures Clap v3-style help menu colors
|
||||||
const STYLES: Styles = Styles::styled()
|
const STYLES: Styles = Styles::styled()
|
||||||
@@ -122,7 +121,7 @@ pub(crate) struct CheckCommand {
|
|||||||
/// The path to a `ty.toml` file to use for configuration.
|
/// The path to a `ty.toml` file to use for configuration.
|
||||||
///
|
///
|
||||||
/// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
|
/// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
|
||||||
#[arg(long, env = EnvVars::TY_CONFIG_FILE, value_name = "PATH")]
|
#[arg(long, env = "TY_CONFIG_FILE", value_name = "PATH")]
|
||||||
pub(crate) config_file: Option<SystemPathBuf>,
|
pub(crate) config_file: Option<SystemPathBuf>,
|
||||||
|
|
||||||
/// The format to use for printing diagnostic messages.
|
/// The format to use for printing diagnostic messages.
|
||||||
@@ -154,17 +153,6 @@ pub(crate) struct CheckCommand {
|
|||||||
#[clap(long, overrides_with("respect_ignore_files"), hide = true)]
|
#[clap(long, overrides_with("respect_ignore_files"), hide = true)]
|
||||||
no_respect_ignore_files: bool,
|
no_respect_ignore_files: bool,
|
||||||
|
|
||||||
/// Enforce exclusions, even for paths passed to ty directly on the command-line.
|
|
||||||
/// Use `--no-force-exclude` to disable.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
overrides_with("no_force_exclude"),
|
|
||||||
help_heading = "File selection"
|
|
||||||
)]
|
|
||||||
force_exclude: bool,
|
|
||||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
|
||||||
no_force_exclude: bool,
|
|
||||||
|
|
||||||
/// Glob patterns for files to exclude from type checking.
|
/// Glob patterns for files to exclude from type checking.
|
||||||
///
|
///
|
||||||
/// Uses gitignore-style syntax to exclude files and directories from type checking.
|
/// Uses gitignore-style syntax to exclude files and directories from type checking.
|
||||||
@@ -189,10 +177,6 @@ pub(crate) struct CheckCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CheckCommand {
|
impl CheckCommand {
|
||||||
pub(crate) fn force_exclude(&self) -> bool {
|
|
||||||
resolve_bool_arg(self.force_exclude, self.no_force_exclude).unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn into_options(self) -> Options {
|
pub(crate) fn into_options(self) -> Options {
|
||||||
let rules = if self.rules.is_empty() {
|
let rules = if self.rules.is_empty() {
|
||||||
None
|
None
|
||||||
@@ -450,12 +434,3 @@ impl ConfigsArg {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
|
||||||
match (yes, no) {
|
|
||||||
(true, false) => Some(true),
|
|
||||||
(false, true) => Some(false),
|
|
||||||
(false, false) => None,
|
|
||||||
(..) => unreachable!("Clap should make this impossible"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
|||||||
.config_file
|
.config_file
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|path| SystemPath::absolute(path, &cwd));
|
.map(|path| SystemPath::absolute(path, &cwd));
|
||||||
let force_exclude = args.force_exclude();
|
|
||||||
|
|
||||||
let mut project_metadata = match &config_file {
|
let mut project_metadata = match &config_file {
|
||||||
Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?,
|
Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?,
|
||||||
@@ -131,13 +130,11 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
|||||||
project_metadata.apply_overrides(&project_options_overrides);
|
project_metadata.apply_overrides(&project_options_overrides);
|
||||||
|
|
||||||
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
||||||
let project = db.project();
|
|
||||||
|
|
||||||
project.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
|
|
||||||
project.set_force_exclude(&mut db, force_exclude);
|
|
||||||
|
|
||||||
|
db.project()
|
||||||
|
.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
|
||||||
if !check_paths.is_empty() {
|
if !check_paths.is_empty() {
|
||||||
project.set_included_paths(&mut db, check_paths);
|
db.project().set_included_paths(&mut db, check_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (main_loop, main_loop_cancellation_token) =
|
let (main_loop, main_loop_cancellation_token) =
|
||||||
@@ -276,6 +273,9 @@ impl MainLoop {
|
|||||||
let mut revision = 0u64;
|
let mut revision = 0u64;
|
||||||
|
|
||||||
while let Ok(message) = self.receiver.recv() {
|
while let Ok(message) = self.receiver.recv() {
|
||||||
|
if self.watcher.is_some() {
|
||||||
|
Printer::clear_screen()?;
|
||||||
|
}
|
||||||
match message {
|
match message {
|
||||||
MainLoopMessage::CheckWorkspace => {
|
MainLoopMessage::CheckWorkspace => {
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
@@ -384,15 +384,12 @@ impl MainLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MainLoopMessage::ApplyChanges(changes) => {
|
MainLoopMessage::ApplyChanges(changes) => {
|
||||||
Printer::clear_screen()?;
|
|
||||||
|
|
||||||
revision += 1;
|
revision += 1;
|
||||||
// Automatically cancels any pending queries and waits for them to complete.
|
// Automatically cancels any pending queries and waits for them to complete.
|
||||||
db.apply_changes(changes, Some(&self.project_options_overrides));
|
db.apply_changes(changes, Some(&self.project_options_overrides));
|
||||||
if let Some(watcher) = self.watcher.as_mut() {
|
if let Some(watcher) = self.watcher.as_mut() {
|
||||||
watcher.update(db);
|
watcher.update(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
||||||
}
|
}
|
||||||
MainLoopMessage::Exit => {
|
MainLoopMessage::Exit => {
|
||||||
|
|||||||
@@ -589,128 +589,6 @@ fn explicit_path_overrides_exclude() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test behavior when explicitly checking a path that matches an exclude pattern and `--force-exclude` is provided
|
|
||||||
#[test]
|
|
||||||
fn explicit_path_overrides_exclude_force_exclude() -> anyhow::Result<()> {
|
|
||||||
let case = CliTest::with_files([
|
|
||||||
(
|
|
||||||
"src/main.py",
|
|
||||||
r#"
|
|
||||||
print(undefined_var) # error: unresolved-reference
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tests/generated.py",
|
|
||||||
r#"
|
|
||||||
print(dist_undefined_var) # error: unresolved-reference
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dist/other.py",
|
|
||||||
r#"
|
|
||||||
print(other_undefined_var) # error: unresolved-reference
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"ty.toml",
|
|
||||||
r#"
|
|
||||||
[src]
|
|
||||||
exclude = ["tests/generated.py"]
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
// Explicitly checking a file in an excluded directory should still check that file
|
|
||||||
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py"), @r"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
|
||||||
--> src/main.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
error[unresolved-reference]: Name `dist_undefined_var` used when not defined
|
|
||||||
--> tests/generated.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(dist_undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
Found 2 diagnostics
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
");
|
|
||||||
|
|
||||||
// Except when `--force-exclude` is set.
|
|
||||||
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py").arg("--force-exclude"), @r"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
|
||||||
--> src/main.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
Found 1 diagnostic
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
");
|
|
||||||
|
|
||||||
// Explicitly checking the entire excluded directory should check all files in it
|
|
||||||
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py"), @r"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
error[unresolved-reference]: Name `other_undefined_var` used when not defined
|
|
||||||
--> dist/other.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(other_undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
|
||||||
--> src/main.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
Found 2 diagnostics
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
");
|
|
||||||
|
|
||||||
// Except when using `--force-exclude`
|
|
||||||
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py").arg("--force-exclude"), @r"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
|
||||||
--> src/main.py:2:7
|
|
||||||
|
|
|
||||||
2 | print(undefined_var) # error: unresolved-reference
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
info: rule `unresolved-reference` is enabled by default
|
|
||||||
|
|
||||||
Found 1 diagnostic
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_and_configuration_exclude() -> anyhow::Result<()> {
|
fn cli_and_configuration_exclude() -> anyhow::Result<()> {
|
||||||
let case = CliTest::with_files([
|
let case = CliTest::with_files([
|
||||||
|
|||||||
@@ -2703,51 +2703,3 @@ fn pythonpath_multiple_dirs_is_respected() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test behavior when `VIRTUAL_ENV` is set but points to a non-existent path.
|
|
||||||
#[test]
|
|
||||||
fn missing_virtual_env() -> anyhow::Result<()> {
|
|
||||||
let working_venv_package1_path = if cfg!(windows) {
|
|
||||||
"project/.venv/Lib/site-packages/package1/__init__.py"
|
|
||||||
} else {
|
|
||||||
"project/.venv/lib/python3.13/site-packages/package1/__init__.py"
|
|
||||||
};
|
|
||||||
|
|
||||||
let case = CliTest::with_files([
|
|
||||||
(
|
|
||||||
"project/test.py",
|
|
||||||
r#"
|
|
||||||
from package1 import WorkingVenv
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"project/.venv/pyvenv.cfg",
|
|
||||||
r#"
|
|
||||||
home = ./
|
|
||||||
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
working_venv_package1_path,
|
|
||||||
r#"
|
|
||||||
class WorkingVenv: ...
|
|
||||||
"#,
|
|
||||||
),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
assert_cmd_snapshot!(case.command()
|
|
||||||
.current_dir(case.root().join("project"))
|
|
||||||
.env("VIRTUAL_ENV", case.root().join("nonexistent-venv")), @r"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ty failed
|
|
||||||
Cause: Failed to discover local Python environment
|
|
||||||
Cause: Invalid `VIRTUAL_ENV` environment variable `<temp_dir>/nonexistent-venv`: does not point to a directory on disk
|
|
||||||
Cause: No such file or directory (os error 2)
|
|
||||||
");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import-deprioritizes-type_check_only,main.py,3,2
|
|||||||
import-deprioritizes-type_check_only,main.py,4,3
|
import-deprioritizes-type_check_only,main.py,4,3
|
||||||
import-keyword-completion,main.py,0,1
|
import-keyword-completion,main.py,0,1
|
||||||
internal-typeshed-hidden,main.py,0,2
|
internal-typeshed-hidden,main.py,0,2
|
||||||
none-completion,main.py,0,1
|
none-completion,main.py,0,2
|
||||||
numpy-array,main.py,0,159
|
numpy-array,main.py,0,159
|
||||||
numpy-array,main.py,1,1
|
numpy-array,main.py,1,1
|
||||||
object-attr-instance-methods,main.py,0,1
|
object-attr-instance-methods,main.py,0,1
|
||||||
@@ -28,4 +28,4 @@ scope-simple-long-identifier,main.py,0,1
|
|||||||
tstring-completions,main.py,0,1
|
tstring-completions,main.py,0,1
|
||||||
ty-extensions-lower-stdlib,main.py,0,9
|
ty-extensions-lower-stdlib,main.py,0,9
|
||||||
type-var-typing-over-ast,main.py,0,3
|
type-var-typing-over-ast,main.py,0,3
|
||||||
type-var-typing-over-ast,main.py,1,253
|
type-var-typing-over-ast,main.py,1,251
|
||||||
|
|||||||
|
@@ -1,8 +1,7 @@
|
|||||||
use crate::completion;
|
use crate::{completion, find_node::covering_node};
|
||||||
|
|
||||||
use ruff_db::{files::File, parsed::parsed_module};
|
use ruff_db::{files::File, parsed::parsed_module};
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
use ruff_python_ast::find_node::covering_node;
|
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use ty_project::Db;
|
use ty_project::Db;
|
||||||
use ty_python_semantic::create_suppression_fix;
|
use ty_python_semantic::create_suppression_fix;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
use crate::AnyNodeRef;
|
use ruff_python_ast::AnyNodeRef;
|
||||||
use crate::visitor::source_order::{SourceOrderVisitor, TraversalSignal, walk_node};
|
use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
@@ -11,7 +11,7 @@ use std::fmt::Formatter;
|
|||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
/// Panics if `range` is not contained within `root`.
|
/// Panics if `range` is not contained within `root`.
|
||||||
pub fn covering_node(root: AnyNodeRef, range: TextRange) -> CoveringNode {
|
pub(crate) fn covering_node(root: AnyNodeRef, range: TextRange) -> CoveringNode {
|
||||||
struct Visitor<'a> {
|
struct Visitor<'a> {
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
found: bool,
|
found: bool,
|
||||||
@@ -48,12 +48,15 @@ pub fn covering_node(root: AnyNodeRef, range: TextRange) -> CoveringNode {
|
|||||||
ancestors: Vec::new(),
|
ancestors: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
walk_node(&mut visitor, root);
|
root.visit_source_order(&mut visitor);
|
||||||
|
if visitor.ancestors.is_empty() {
|
||||||
|
visitor.ancestors.push(root);
|
||||||
|
}
|
||||||
CoveringNode::from_ancestors(visitor.ancestors)
|
CoveringNode::from_ancestors(visitor.ancestors)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The node with a minimal range that fully contains the search range.
|
/// The node with a minimal range that fully contains the search range.
|
||||||
pub struct CoveringNode<'a> {
|
pub(crate) struct CoveringNode<'a> {
|
||||||
/// The covering node, along with all of its ancestors up to the
|
/// The covering node, along with all of its ancestors up to the
|
||||||
/// root. The root is always the first element and the covering
|
/// root. The root is always the first element and the covering
|
||||||
/// node found is always the last node. This sequence is guaranteed
|
/// node found is always the last node. This sequence is guaranteed
|
||||||
@@ -64,12 +67,12 @@ pub struct CoveringNode<'a> {
|
|||||||
impl<'a> CoveringNode<'a> {
|
impl<'a> CoveringNode<'a> {
|
||||||
/// Creates a new `CoveringNode` from a list of ancestor nodes.
|
/// Creates a new `CoveringNode` from a list of ancestor nodes.
|
||||||
/// The ancestors should be ordered from root to the covering node.
|
/// The ancestors should be ordered from root to the covering node.
|
||||||
pub fn from_ancestors(ancestors: Vec<AnyNodeRef<'a>>) -> Self {
|
pub(crate) fn from_ancestors(ancestors: Vec<AnyNodeRef<'a>>) -> Self {
|
||||||
Self { nodes: ancestors }
|
Self { nodes: ancestors }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the covering node found.
|
/// Returns the covering node found.
|
||||||
pub fn node(&self) -> AnyNodeRef<'a> {
|
pub(crate) fn node(&self) -> AnyNodeRef<'a> {
|
||||||
*self
|
*self
|
||||||
.nodes
|
.nodes
|
||||||
.last()
|
.last()
|
||||||
@@ -77,7 +80,7 @@ impl<'a> CoveringNode<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the node's parent.
|
/// Returns the node's parent.
|
||||||
pub fn parent(&self) -> Option<AnyNodeRef<'a>> {
|
pub(crate) fn parent(&self) -> Option<AnyNodeRef<'a>> {
|
||||||
let penultimate = self.nodes.len().checked_sub(2)?;
|
let penultimate = self.nodes.len().checked_sub(2)?;
|
||||||
self.nodes.get(penultimate).copied()
|
self.nodes.get(penultimate).copied()
|
||||||
}
|
}
|
||||||
@@ -87,7 +90,7 @@ impl<'a> CoveringNode<'a> {
|
|||||||
///
|
///
|
||||||
/// The "first" here means that the node closest to a leaf is
|
/// The "first" here means that the node closest to a leaf is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn find_first(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result<Self, Self> {
|
pub(crate) fn find_first(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result<Self, Self> {
|
||||||
let Some(index) = self.find_first_index(f) else {
|
let Some(index) = self.find_first_index(f) else {
|
||||||
return Err(self);
|
return Err(self);
|
||||||
};
|
};
|
||||||
@@ -102,7 +105,7 @@ impl<'a> CoveringNode<'a> {
|
|||||||
/// the highest ancestor found satisfying the given predicate is
|
/// the highest ancestor found satisfying the given predicate is
|
||||||
/// returned. Note that this is *not* the same as finding the node
|
/// returned. Note that this is *not* the same as finding the node
|
||||||
/// closest to the root that satisfies the given predictate.
|
/// closest to the root that satisfies the given predictate.
|
||||||
pub fn find_last(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result<Self, Self> {
|
pub(crate) fn find_last(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result<Self, Self> {
|
||||||
let Some(mut index) = self.find_first_index(&f) else {
|
let Some(mut index) = self.find_first_index(&f) else {
|
||||||
return Err(self);
|
return Err(self);
|
||||||
};
|
};
|
||||||
@@ -115,7 +118,7 @@ impl<'a> CoveringNode<'a> {
|
|||||||
|
|
||||||
/// Returns an iterator over the ancestor nodes, starting with the node itself
|
/// Returns an iterator over the ancestor nodes, starting with the node itself
|
||||||
/// and walking towards the root.
|
/// and walking towards the root.
|
||||||
pub fn ancestors(&self) -> impl DoubleEndedIterator<Item = AnyNodeRef<'a>> + '_ {
|
pub(crate) fn ancestors(&self) -> impl DoubleEndedIterator<Item = AnyNodeRef<'a>> + '_ {
|
||||||
self.nodes.iter().copied().rev()
|
self.nodes.iter().copied().rev()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@ pub use crate::goto_type_definition::goto_type_definition;
|
|||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::find_node::covering_node;
|
||||||
use crate::stub_mapping::StubMapper;
|
use crate::stub_mapping::StubMapper;
|
||||||
use ruff_db::parsed::ParsedModuleRef;
|
use ruff_db::parsed::ParsedModuleRef;
|
||||||
use ruff_python_ast::find_node::{CoveringNode, covering_node};
|
|
||||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
@@ -665,7 +665,7 @@ impl GotoTarget<'_> {
|
|||||||
/// Creates a `GotoTarget` from a `CoveringNode` and an offset within the node
|
/// Creates a `GotoTarget` from a `CoveringNode` and an offset within the node
|
||||||
pub(crate) fn from_covering_node<'a>(
|
pub(crate) fn from_covering_node<'a>(
|
||||||
model: &SemanticModel,
|
model: &SemanticModel,
|
||||||
covering_node: &CoveringNode<'a>,
|
covering_node: &crate::find_node::CoveringNode<'a>,
|
||||||
offset: TextSize,
|
offset: TextSize,
|
||||||
tokens: &Tokens,
|
tokens: &Tokens,
|
||||||
) -> Option<GotoTarget<'a>> {
|
) -> Option<GotoTarget<'a>> {
|
||||||
|
|||||||
@@ -386,29 +386,6 @@ FOO = 0
|
|||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn goto_declaration_from_import_rhs_is_module() {
|
|
||||||
let test = CursorTest::builder()
|
|
||||||
.source("lib/__init__.py", r#""#)
|
|
||||||
.source("lib/module.py", r#""#)
|
|
||||||
.source("main.py", r#"from lib import module<CURSOR>"#)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Should resolve to the actual function definition, not the import statement
|
|
||||||
assert_snapshot!(test.goto_declaration(), @r"
|
|
||||||
info[goto-declaration]: Go to declaration
|
|
||||||
--> main.py:1:17
|
|
||||||
|
|
|
||||||
1 | from lib import module
|
|
||||||
| ^^^^^^ Clicking here
|
|
||||||
|
|
|
||||||
info: Found 1 declaration
|
|
||||||
--> lib/module.py:1:1
|
|
||||||
|
|
|
||||||
|
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_declaration_from_import_as() {
|
fn goto_declaration_from_import_as() {
|
||||||
let test = CursorTest::builder()
|
let test = CursorTest::builder()
|
||||||
|
|||||||
@@ -1339,13 +1339,13 @@ f(**kwargs<CURSOR>)
|
|||||||
| ^^^^^^ Clicking here
|
| ^^^^^^ Clicking here
|
||||||
|
|
|
|
||||||
info: Found 1 type definition
|
info: Found 1 type definition
|
||||||
--> stdlib/builtins.pyi:2947:7
|
--> stdlib/builtins.pyi:2920:7
|
||||||
|
|
|
|
||||||
2946 | @disjoint_base
|
2919 | @disjoint_base
|
||||||
2947 | class dict(MutableMapping[_KT, _VT]):
|
2920 | class dict(MutableMapping[_KT, _VT]):
|
||||||
| ----
|
| ----
|
||||||
2948 | """dict() -> new empty dictionary
|
2921 | """dict() -> new empty dictionary
|
||||||
2949 | dict(mapping) -> new dictionary initialized from a mapping object's
|
2922 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||||
|
|
|
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -745,17 +745,8 @@ impl ImportResponseKind<'_> {
|
|||||||
fn priority(&self) -> usize {
|
fn priority(&self) -> usize {
|
||||||
match *self {
|
match *self {
|
||||||
ImportResponseKind::Unqualified { .. } => 0,
|
ImportResponseKind::Unqualified { .. } => 0,
|
||||||
ImportResponseKind::Partial(_) => 1,
|
ImportResponseKind::Qualified { .. } => 1,
|
||||||
// N.B. When given the choice between adding a
|
ImportResponseKind::Partial(_) => 2,
|
||||||
// name to an existing `from ... import ...`
|
|
||||||
// statement and using an existing `import ...`
|
|
||||||
// in a qualified manner, we currently choose
|
|
||||||
// the former. Originally we preferred qualification,
|
|
||||||
// but there is some evidence that this violates
|
|
||||||
// expectations.
|
|
||||||
//
|
|
||||||
// Ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3352233790
|
|
||||||
ImportResponseKind::Qualified { .. } => 2,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -869,6 +860,7 @@ mod tests {
|
|||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
use insta::internals::SettingsBindDropGuard;
|
use insta::internals::SettingsBindDropGuard;
|
||||||
|
|
||||||
|
use crate::find_node::covering_node;
|
||||||
use crate::tests::{CursorTest, CursorTestBuilder, cursor_test};
|
use crate::tests::{CursorTest, CursorTestBuilder, cursor_test};
|
||||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
|
||||||
use ruff_db::files::{File, FileRootKind, system_path_to_file};
|
use ruff_db::files::{File, FileRootKind, system_path_to_file};
|
||||||
@@ -876,7 +868,6 @@ mod tests {
|
|||||||
use ruff_db::source::source_text;
|
use ruff_db::source::source_text;
|
||||||
use ruff_db::system::{DbWithWritableSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{DbWithWritableSystem, SystemPath, SystemPathBuf};
|
||||||
use ruff_db::{Db, system};
|
use ruff_db::{Db, system};
|
||||||
use ruff_python_ast::find_node::covering_node;
|
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_trivia::textwrap::dedent;
|
use ruff_python_trivia::textwrap::dedent;
|
||||||
use ruff_text_size::TextSize;
|
use ruff_text_size::TextSize;
|
||||||
@@ -1341,9 +1332,9 @@ import collections
|
|||||||
);
|
);
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
test.import("collections", "defaultdict"), @r"
|
test.import("collections", "defaultdict"), @r"
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict
|
||||||
import collections
|
import collections
|
||||||
defaultdict
|
collections.defaultdict
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1241,12 +1241,12 @@ mod tests {
|
|||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:8:5
|
--> main2.py:8:5
|
||||||
@@ -1335,12 +1335,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:9:5
|
--> main2.py:9:5
|
||||||
@@ -1391,12 +1391,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:10:5
|
--> main2.py:10:5
|
||||||
@@ -2217,12 +2217,12 @@ mod tests {
|
|||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:2:5
|
--> main2.py:2:5
|
||||||
@@ -2270,12 +2270,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:3:5
|
--> main2.py:3:5
|
||||||
@@ -2325,12 +2325,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:5
|
--> main2.py:4:5
|
||||||
@@ -2364,13 +2364,13 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2618:7
|
--> stdlib/builtins.pyi:2591:7
|
||||||
|
|
|
|
||||||
2617 | @final
|
2590 | @final
|
||||||
2618 | class bool(int):
|
2591 | class bool(int):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2619 | """Returns True when the argument is true, False otherwise.
|
2592 | """Returns True when the argument is true, False otherwise.
|
||||||
2620 | The builtins True and False are the only two instances of the class bool.
|
2593 | The builtins True and False are the only two instances of the class bool.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:20
|
--> main2.py:4:20
|
||||||
@@ -2384,12 +2384,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:5:5
|
--> main2.py:5:5
|
||||||
@@ -2443,12 +2443,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:6:5
|
--> main2.py:6:5
|
||||||
@@ -2502,12 +2502,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:7:5
|
--> main2.py:7:5
|
||||||
@@ -2561,12 +2561,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:8:5
|
--> main2.py:8:5
|
||||||
@@ -2620,12 +2620,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:9:5
|
--> main2.py:9:5
|
||||||
@@ -2678,12 +2678,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:10:5
|
--> main2.py:10:5
|
||||||
@@ -2737,12 +2737,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:11:5
|
--> main2.py:11:5
|
||||||
@@ -2811,12 +2811,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:12:5
|
--> main2.py:12:5
|
||||||
@@ -2925,12 +2925,12 @@ mod tests {
|
|||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:7:5
|
--> main2.py:7:5
|
||||||
@@ -3092,12 +3092,12 @@ mod tests {
|
|||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:18
|
--> main2.py:4:18
|
||||||
@@ -3110,12 +3110,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:5:18
|
--> main2.py:5:18
|
||||||
@@ -3248,12 +3248,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2722:7
|
--> stdlib/builtins.pyi:2695:7
|
||||||
|
|
|
|
||||||
2721 | @disjoint_base
|
2694 | @disjoint_base
|
||||||
2722 | class tuple(Sequence[_T_co]):
|
2695 | class tuple(Sequence[_T_co]):
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2723 | """Built-in immutable sequence.
|
2696 | """Built-in immutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:8:5
|
--> main2.py:8:5
|
||||||
@@ -4250,12 +4250,12 @@ mod tests {
|
|||||||
foo([x=]y[0])
|
foo([x=]y[0])
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:3:5
|
--> main2.py:3:5
|
||||||
@@ -4303,12 +4303,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:5
|
--> main2.py:4:5
|
||||||
@@ -5609,12 +5609,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:3:14
|
--> main2.py:3:14
|
||||||
@@ -6046,13 +6046,13 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2618:7
|
--> stdlib/builtins.pyi:2591:7
|
||||||
|
|
|
|
||||||
2617 | @final
|
2590 | @final
|
||||||
2618 | class bool(int):
|
2591 | class bool(int):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2619 | """Returns True when the argument is true, False otherwise.
|
2592 | """Returns True when the argument is true, False otherwise.
|
||||||
2620 | The builtins True and False are the only two instances of the class bool.
|
2593 | The builtins True and False are the only two instances of the class bool.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:25
|
--> main2.py:4:25
|
||||||
@@ -6100,12 +6100,12 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/builtins.pyi:2829:7
|
--> stdlib/builtins.pyi:2802:7
|
||||||
|
|
|
|
||||||
2828 | @disjoint_base
|
2801 | @disjoint_base
|
||||||
2829 | class list(MutableSequence[_T]):
|
2802 | class list(MutableSequence[_T]):
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2830 | """Built-in mutable sequence.
|
2803 | """Built-in mutable sequence.
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:4:49
|
--> main2.py:4:49
|
||||||
@@ -6216,7 +6216,7 @@ mod tests {
|
|||||||
assert_snapshot!(test.inlay_hints(), @r#"
|
assert_snapshot!(test.inlay_hints(), @r#"
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/typing.pyi:351:1
|
--> stdlib/typing.pyi:351:1
|
||||||
@@ -6232,7 +6232,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Literal
|
2 | from typing import Literal
|
||||||
3 |
|
3 |
|
||||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
@@ -6250,7 +6250,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Literal
|
2 | from typing import Literal
|
||||||
3 |
|
3 |
|
||||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
@@ -6268,7 +6268,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Literal
|
2 | from typing import Literal
|
||||||
3 |
|
3 |
|
||||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
@@ -6286,7 +6286,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Literal
|
2 | from typing import Literal
|
||||||
3 |
|
3 |
|
||||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
"#);
|
"#);
|
||||||
@@ -6636,7 +6636,7 @@ mod tests {
|
|||||||
assert_snapshot!(test.inlay_hints(), @r"
|
assert_snapshot!(test.inlay_hints(), @r"
|
||||||
from typing import Protocol, TypeVar
|
from typing import Protocol, TypeVar
|
||||||
T = TypeVar([name=]'T')
|
T = TypeVar([name=]'T')
|
||||||
Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/typing.pyi:276:13
|
--> stdlib/typing.pyi:276:13
|
||||||
@@ -6654,7 +6654,7 @@ mod tests {
|
|||||||
2 | from typing import Protocol, TypeVar
|
2 | from typing import Protocol, TypeVar
|
||||||
3 | T = TypeVar([name=]'T')
|
3 | T = TypeVar([name=]'T')
|
||||||
| ^^^^
|
| ^^^^
|
||||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
@@ -6671,7 +6671,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Protocol, TypeVar
|
2 | from typing import Protocol, TypeVar
|
||||||
3 | T = TypeVar([name=]'T')
|
3 | T = TypeVar([name=]'T')
|
||||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
@@ -6688,7 +6688,7 @@ mod tests {
|
|||||||
|
|
|
|
||||||
2 | from typing import Protocol, TypeVar
|
2 | from typing import Protocol, TypeVar
|
||||||
3 | T = TypeVar([name=]'T')
|
3 | T = TypeVar([name=]'T')
|
||||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
");
|
");
|
||||||
@@ -6739,14 +6739,14 @@ mod tests {
|
|||||||
A = TypeAliasType([name=]'A', [value=]str)
|
A = TypeAliasType([name=]'A', [value=]str)
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/typing.pyi:2037:26
|
--> stdlib/typing.pyi:2032:26
|
||||||
|
|
|
|
||||||
2035 | """
|
2030 | """
|
||||||
2036 |
|
2031 |
|
||||||
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||||
| ^^^^
|
| ^^^^
|
||||||
2038 | @property
|
2033 | @property
|
||||||
2039 | def __value__(self) -> Any: ... # AnnotationForm
|
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:3:20
|
--> main2.py:3:20
|
||||||
@@ -6757,14 +6757,14 @@ mod tests {
|
|||||||
|
|
|
|
||||||
|
|
||||||
info[inlay-hint-location]: Inlay Hint Target
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
--> stdlib/typing.pyi:2037:37
|
--> stdlib/typing.pyi:2032:37
|
||||||
|
|
|
|
||||||
2035 | """
|
2030 | """
|
||||||
2036 |
|
2031 |
|
||||||
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
2038 | @property
|
2033 | @property
|
||||||
2039 | def __value__(self) -> Any: ... # AnnotationForm
|
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||||
|
|
|
|
||||||
info: Source
|
info: Source
|
||||||
--> main2.py:3:32
|
--> main2.py:3:32
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ mod completion;
|
|||||||
mod doc_highlights;
|
mod doc_highlights;
|
||||||
mod docstring;
|
mod docstring;
|
||||||
mod document_symbols;
|
mod document_symbols;
|
||||||
|
mod find_node;
|
||||||
mod find_references;
|
mod find_references;
|
||||||
mod goto;
|
mod goto;
|
||||||
mod goto_declaration;
|
mod goto_declaration;
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
//! all references to these externally-visible symbols therefore requires
|
//! all references to these externally-visible symbols therefore requires
|
||||||
//! an expensive search of all source files in the workspace.
|
//! an expensive search of all source files in the workspace.
|
||||||
|
|
||||||
|
use crate::find_node::CoveringNode;
|
||||||
use crate::goto::GotoTarget;
|
use crate::goto::GotoTarget;
|
||||||
use crate::{Db, NavigationTargets, ReferenceKind, ReferenceTarget};
|
use crate::{Db, NavigationTargets, ReferenceKind, ReferenceTarget};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast::find_node::CoveringNode;
|
|
||||||
use ruff_python_ast::token::Tokens;
|
use ruff_python_ast::token::Tokens;
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, AnyNodeRef,
|
self as ast, AnyNodeRef,
|
||||||
@@ -334,7 +334,10 @@ impl LocalReferencesFinder<'_> {
|
|||||||
|
|
||||||
/// Determines whether the given covering node is a reference to
|
/// Determines whether the given covering node is a reference to
|
||||||
/// the symbol we are searching for
|
/// the symbol we are searching for
|
||||||
fn check_reference_from_covering_node(&mut self, covering_node: &CoveringNode<'_>) {
|
fn check_reference_from_covering_node(
|
||||||
|
&mut self,
|
||||||
|
covering_node: &crate::find_node::CoveringNode<'_>,
|
||||||
|
) {
|
||||||
// Use the start of the covering node as the offset. Any offset within
|
// Use the start of the covering node as the offset. Any offset within
|
||||||
// the node is fine here. Offsets matter only for import statements
|
// the node is fine here. Offsets matter only for import statements
|
||||||
// where the identifier might be a multi-part module name.
|
// where the identifier might be a multi-part module name.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::find_node::covering_node;
|
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
use crate::find_node::covering_node;
|
||||||
|
|
||||||
/// Returns a list of nested selection ranges, where each range contains the next one.
|
/// Returns a list of nested selection ranges, where each range contains the next one.
|
||||||
/// The first range in the list is the largest range containing the cursor position.
|
/// The first range in the list is the largest range containing the cursor position.
|
||||||
@@ -66,28 +66,20 @@ x = 1 + <CURSOR>2
|
|||||||
|
|
||||||
assert_snapshot!(test.selection_range(), @r"
|
assert_snapshot!(test.selection_range(), @r"
|
||||||
info[selection-range]: Selection Range 0
|
info[selection-range]: Selection Range 0
|
||||||
--> main.py:1:1
|
|
||||||
|
|
|
||||||
1 | /
|
|
||||||
2 | | x = 1 + 2
|
|
||||||
| |__________^
|
|
||||||
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 1
|
|
||||||
--> main.py:2:1
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | x = 1 + 2
|
2 | x = 1 + 2
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 2
|
info[selection-range]: Selection Range 1
|
||||||
--> main.py:2:5
|
--> main.py:2:5
|
||||||
|
|
|
|
||||||
2 | x = 1 + 2
|
2 | x = 1 + 2
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 3
|
info[selection-range]: Selection Range 2
|
||||||
--> main.py:2:9
|
--> main.py:2:9
|
||||||
|
|
|
|
||||||
2 | x = 1 + 2
|
2 | x = 1 + 2
|
||||||
@@ -110,28 +102,20 @@ print(\"he<CURSOR>llo\")
|
|||||||
|
|
||||||
assert_snapshot!(test.selection_range(), @r#"
|
assert_snapshot!(test.selection_range(), @r#"
|
||||||
info[selection-range]: Selection Range 0
|
info[selection-range]: Selection Range 0
|
||||||
--> main.py:1:1
|
|
||||||
|
|
|
||||||
1 | /
|
|
||||||
2 | | print("hello")
|
|
||||||
| |_______________^
|
|
||||||
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 1
|
|
||||||
--> main.py:2:1
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | print("hello")
|
2 | print("hello")
|
||||||
| ^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 2
|
info[selection-range]: Selection Range 1
|
||||||
--> main.py:2:6
|
--> main.py:2:6
|
||||||
|
|
|
|
||||||
2 | print("hello")
|
2 | print("hello")
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 3
|
info[selection-range]: Selection Range 2
|
||||||
--> main.py:2:7
|
--> main.py:2:7
|
||||||
|
|
|
|
||||||
2 | print("hello")
|
2 | print("hello")
|
||||||
@@ -155,15 +139,6 @@ def my_<CURSOR>function():
|
|||||||
|
|
||||||
assert_snapshot!(test.selection_range(), @r"
|
assert_snapshot!(test.selection_range(), @r"
|
||||||
info[selection-range]: Selection Range 0
|
info[selection-range]: Selection Range 0
|
||||||
--> main.py:1:1
|
|
||||||
|
|
|
||||||
1 | /
|
|
||||||
2 | | def my_function():
|
|
||||||
3 | | return 42
|
|
||||||
| |______________^
|
|
||||||
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 1
|
|
||||||
--> main.py:2:1
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | / def my_function():
|
2 | / def my_function():
|
||||||
@@ -171,7 +146,7 @@ def my_<CURSOR>function():
|
|||||||
| |_____________^
|
| |_____________^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 2
|
info[selection-range]: Selection Range 1
|
||||||
--> main.py:2:5
|
--> main.py:2:5
|
||||||
|
|
|
|
||||||
2 | def my_function():
|
2 | def my_function():
|
||||||
@@ -197,16 +172,6 @@ class My<CURSOR>Class:
|
|||||||
|
|
||||||
assert_snapshot!(test.selection_range(), @r"
|
assert_snapshot!(test.selection_range(), @r"
|
||||||
info[selection-range]: Selection Range 0
|
info[selection-range]: Selection Range 0
|
||||||
--> main.py:1:1
|
|
||||||
|
|
|
||||||
1 | /
|
|
||||||
2 | | class MyClass:
|
|
||||||
3 | | def __init__(self):
|
|
||||||
4 | | self.value = 1
|
|
||||||
| |_______________________^
|
|
||||||
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 1
|
|
||||||
--> main.py:2:1
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | / class MyClass:
|
2 | / class MyClass:
|
||||||
@@ -215,7 +180,7 @@ class My<CURSOR>Class:
|
|||||||
| |______________________^
|
| |______________________^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 2
|
info[selection-range]: Selection Range 1
|
||||||
--> main.py:2:7
|
--> main.py:2:7
|
||||||
|
|
|
|
||||||
2 | class MyClass:
|
2 | class MyClass:
|
||||||
@@ -240,56 +205,48 @@ result = [(lambda x: x[key.<CURSOR>attr])(item) for item in data if item is not
|
|||||||
|
|
||||||
assert_snapshot!(test.selection_range(), @r"
|
assert_snapshot!(test.selection_range(), @r"
|
||||||
info[selection-range]: Selection Range 0
|
info[selection-range]: Selection Range 0
|
||||||
--> main.py:1:1
|
|
||||||
|
|
|
||||||
1 | /
|
|
||||||
2 | | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
|
||||||
| |______________________________________________________________________________^
|
|
||||||
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 1
|
|
||||||
--> main.py:2:1
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 2
|
info[selection-range]: Selection Range 1
|
||||||
--> main.py:2:10
|
--> main.py:2:10
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 3
|
info[selection-range]: Selection Range 2
|
||||||
--> main.py:2:11
|
--> main.py:2:11
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 4
|
info[selection-range]: Selection Range 3
|
||||||
--> main.py:2:12
|
--> main.py:2:12
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 5
|
info[selection-range]: Selection Range 4
|
||||||
--> main.py:2:22
|
--> main.py:2:22
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 6
|
info[selection-range]: Selection Range 5
|
||||||
--> main.py:2:24
|
--> main.py:2:24
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
info[selection-range]: Selection Range 7
|
info[selection-range]: Selection Range 6
|
||||||
--> main.py:2:28
|
--> main.py:2:28
|
||||||
|
|
|
|
||||||
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
2 | result = [(lambda x: x[key.attr])(item) for item in data if item is not None]
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ use bitflags::bitflags;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::visitor::source_order::{
|
use ruff_python_ast::visitor::source_order::{
|
||||||
SourceOrderVisitor, TraversalSignal, walk_arguments, walk_expr, walk_stmt,
|
SourceOrderVisitor, TraversalSignal, walk_expr, walk_stmt,
|
||||||
};
|
};
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, AnyNodeRef, BytesLiteral, Expr, FString, InterpolatedStringElement, Stmt,
|
AnyNodeRef, BytesLiteral, Expr, FString, InterpolatedStringElement, Stmt, StringLiteral,
|
||||||
StringLiteral, TypeParam,
|
TypeParam,
|
||||||
};
|
};
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -131,7 +132,6 @@ bitflags! {
|
|||||||
const DEFINITION = 1 << 0;
|
const DEFINITION = 1 << 0;
|
||||||
const READONLY = 1 << 1;
|
const READONLY = 1 << 1;
|
||||||
const ASYNC = 1 << 2;
|
const ASYNC = 1 << 2;
|
||||||
const DOCUMENTATION = 1 << 3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ impl SemanticTokenModifier {
|
|||||||
/// highlighting. For details, refer to this LSP specification:
|
/// highlighting. For details, refer to this LSP specification:
|
||||||
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenModifiers>
|
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenModifiers>
|
||||||
pub fn all_names() -> Vec<&'static str> {
|
pub fn all_names() -> Vec<&'static str> {
|
||||||
vec!["definition", "readonly", "async", "documentation"]
|
vec!["definition", "readonly", "async"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,22 +190,18 @@ pub fn semantic_tokens(db: &dyn Db, file: File, range: Option<TextRange>) -> Sem
|
|||||||
let model = SemanticModel::new(db, file);
|
let model = SemanticModel::new(db, file);
|
||||||
|
|
||||||
let mut visitor = SemanticTokenVisitor::new(&model, range);
|
let mut visitor = SemanticTokenVisitor::new(&model, range);
|
||||||
visitor.expecting_docstring = true;
|
|
||||||
visitor.visit_body(parsed.suite());
|
visitor.visit_body(parsed.suite());
|
||||||
|
|
||||||
SemanticTokens::new(visitor.tokens)
|
SemanticTokens::new(visitor.tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AST visitor that collects semantic tokens.
|
/// AST visitor that collects semantic tokens.
|
||||||
#[expect(clippy::struct_excessive_bools)]
|
|
||||||
struct SemanticTokenVisitor<'db> {
|
struct SemanticTokenVisitor<'db> {
|
||||||
model: &'db SemanticModel<'db>,
|
model: &'db SemanticModel<'db>,
|
||||||
tokens: Vec<SemanticToken>,
|
tokens: Vec<SemanticToken>,
|
||||||
in_class_scope: bool,
|
in_class_scope: bool,
|
||||||
in_type_annotation: bool,
|
in_type_annotation: bool,
|
||||||
in_target_creating_definition: bool,
|
in_target_creating_definition: bool,
|
||||||
in_docstring: bool,
|
|
||||||
expecting_docstring: bool,
|
|
||||||
range_filter: Option<TextRange>,
|
range_filter: Option<TextRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +213,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
|||||||
in_class_scope: false,
|
in_class_scope: false,
|
||||||
in_target_creating_definition: false,
|
in_target_creating_definition: false,
|
||||||
in_type_annotation: false,
|
in_type_annotation: false,
|
||||||
in_docstring: false,
|
|
||||||
range_filter,
|
range_filter,
|
||||||
expecting_docstring: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,9 +254,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_constant_name(name: &str) -> bool {
|
fn is_constant_name(name: &str) -> bool {
|
||||||
name.chars()
|
name.chars().all(|c| c.is_uppercase() || c == '_') && name.len() > 1
|
||||||
.all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
|
|
||||||
&& name.len() > 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
||||||
@@ -608,8 +600,6 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||||
let expecting_docstring = self.expecting_docstring;
|
|
||||||
self.expecting_docstring = false;
|
|
||||||
match stmt {
|
match stmt {
|
||||||
ast::Stmt::FunctionDef(func) => {
|
ast::Stmt::FunctionDef(func) => {
|
||||||
// Visit decorator expressions
|
// Visit decorator expressions
|
||||||
@@ -651,9 +641,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
let prev_in_class = self.in_class_scope;
|
let prev_in_class = self.in_class_scope;
|
||||||
|
|
||||||
self.in_class_scope = false;
|
self.in_class_scope = false;
|
||||||
self.expecting_docstring = true;
|
|
||||||
self.visit_body(&func.body);
|
self.visit_body(&func.body);
|
||||||
self.expecting_docstring = false;
|
|
||||||
self.in_class_scope = prev_in_class;
|
self.in_class_scope = prev_in_class;
|
||||||
}
|
}
|
||||||
ast::Stmt::ClassDef(class) => {
|
ast::Stmt::ClassDef(class) => {
|
||||||
@@ -678,14 +666,19 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
|
|
||||||
// Handle base classes and type annotations in inheritance
|
// Handle base classes and type annotations in inheritance
|
||||||
if let Some(arguments) = &class.arguments {
|
if let Some(arguments) = &class.arguments {
|
||||||
walk_arguments(self, arguments);
|
// Visit base class arguments
|
||||||
|
for arg in &arguments.args {
|
||||||
|
self.visit_expr(arg);
|
||||||
|
}
|
||||||
|
// Visit keyword arguments (for metaclass, etc.)
|
||||||
|
for keyword in &arguments.keywords {
|
||||||
|
self.visit_expr(&keyword.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_in_class = self.in_class_scope;
|
let prev_in_class = self.in_class_scope;
|
||||||
self.in_class_scope = true;
|
self.in_class_scope = true;
|
||||||
self.expecting_docstring = true;
|
|
||||||
self.visit_body(&class.body);
|
self.visit_body(&class.body);
|
||||||
self.expecting_docstring = false;
|
|
||||||
self.in_class_scope = prev_in_class;
|
self.in_class_scope = prev_in_class;
|
||||||
}
|
}
|
||||||
ast::Stmt::TypeAlias(type_alias) => {
|
ast::Stmt::TypeAlias(type_alias) => {
|
||||||
@@ -767,7 +760,6 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
self.in_target_creating_definition = false;
|
self.in_target_creating_definition = false;
|
||||||
|
|
||||||
self.visit_expr(&assignment.value);
|
self.visit_expr(&assignment.value);
|
||||||
self.expecting_docstring = true;
|
|
||||||
}
|
}
|
||||||
ast::Stmt::AnnAssign(assignment) => {
|
ast::Stmt::AnnAssign(assignment) => {
|
||||||
self.in_target_creating_definition = true;
|
self.in_target_creating_definition = true;
|
||||||
@@ -779,7 +771,6 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
if let Some(value) = &assignment.value {
|
if let Some(value) = &assignment.value {
|
||||||
self.visit_expr(value);
|
self.visit_expr(value);
|
||||||
}
|
}
|
||||||
self.expecting_docstring = true;
|
|
||||||
}
|
}
|
||||||
ast::Stmt::For(for_stmt) => {
|
ast::Stmt::For(for_stmt) => {
|
||||||
self.in_target_creating_definition = true;
|
self.in_target_creating_definition = true;
|
||||||
@@ -824,13 +815,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
self.visit_body(&try_stmt.orelse);
|
self.visit_body(&try_stmt.orelse);
|
||||||
self.visit_body(&try_stmt.finalbody);
|
self.visit_body(&try_stmt.finalbody);
|
||||||
}
|
}
|
||||||
ast::Stmt::Expr(expr) => {
|
|
||||||
if expecting_docstring && expr.value.is_string_literal_expr() {
|
|
||||||
self.in_docstring = true;
|
|
||||||
}
|
|
||||||
walk_stmt(self, stmt);
|
|
||||||
self.in_docstring = false;
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
// For all other statement types, let the default visitor handle them
|
// For all other statement types, let the default visitor handle them
|
||||||
walk_stmt(self, stmt);
|
walk_stmt(self, stmt);
|
||||||
@@ -924,12 +909,11 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
|||||||
|
|
||||||
fn visit_string_literal(&mut self, string_literal: &StringLiteral) {
|
fn visit_string_literal(&mut self, string_literal: &StringLiteral) {
|
||||||
// Emit a semantic token for this string literal part
|
// Emit a semantic token for this string literal part
|
||||||
let modifiers = if self.in_docstring {
|
self.add_token(
|
||||||
SemanticTokenModifier::DOCUMENTATION
|
string_literal.range(),
|
||||||
} else {
|
SemanticTokenType::String,
|
||||||
SemanticTokenModifier::empty()
|
SemanticTokenModifier::empty(),
|
||||||
};
|
);
|
||||||
self.add_token(string_literal.range(), SemanticTokenType::String, modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_bytes_literal(&mut self, bytes_literal: &BytesLiteral) {
|
fn visit_bytes_literal(&mut self, bytes_literal: &BytesLiteral) {
|
||||||
@@ -1152,22 +1136,6 @@ mod tests {
|
|||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_semantic_tokens_class_args() {
|
|
||||||
// This used to cause a panic because of an incorrect
|
|
||||||
// insertion-order when visiting arguments inside
|
|
||||||
// class definitions.
|
|
||||||
let test = SemanticTokenTest::new("class Foo(m=x, m)");
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r###"
|
|
||||||
"Foo" @ 6..9: Class [definition]
|
|
||||||
"x" @ 12..13: Variable
|
|
||||||
"m" @ 15..16: Variable
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_semantic_tokens_variables() {
|
fn test_semantic_tokens_variables() {
|
||||||
let test = SemanticTokenTest::new(
|
let test = SemanticTokenTest::new(
|
||||||
@@ -1874,456 +1842,6 @@ y: Optional[str] = None
|
|||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn function_docstring_classification() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
def my_function(param1: int, param2: str) -> bool:
|
|
||||||
"""Example function
|
|
||||||
|
|
||||||
Args:
|
|
||||||
param1: The first parameter.
|
|
||||||
param2: The second parameter.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The return value. True for success, False otherwise.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
x = "hello"
|
|
||||||
def other_func(): pass
|
|
||||||
|
|
||||||
"""unrelated string"""
|
|
||||||
|
|
||||||
return False
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"my_function" @ 5..16: Function [definition]
|
|
||||||
"param1" @ 17..23: Parameter [definition]
|
|
||||||
"int" @ 25..28: Class
|
|
||||||
"param2" @ 30..36: Parameter [definition]
|
|
||||||
"str" @ 38..41: Class
|
|
||||||
"bool" @ 46..50: Class
|
|
||||||
"\"\"\"Example function\n\n Args:\n param1: The first parameter.\n param2: The second parameter.\n\n Returns:\n The return value. True for success, False otherwise.\n\n \"\"\"" @ 56..245: String [documentation]
|
|
||||||
"x" @ 251..252: Variable [definition]
|
|
||||||
"\"hello\"" @ 255..262: String
|
|
||||||
"other_func" @ 271..281: Function [definition]
|
|
||||||
"\"\"\"unrelated string\"\"\"" @ 295..317: String
|
|
||||||
"False" @ 330..335: BuiltinConstant
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn class_docstring_classification() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"""Example class
|
|
||||||
|
|
||||||
What a good class wowwee
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self): pass
|
|
||||||
|
|
||||||
"""unrelated string"""
|
|
||||||
|
|
||||||
x: str = "hello"
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"Example class\n\n What a good class wowwee\n \"\"\"" @ 20..74: String [documentation]
|
|
||||||
"__init__" @ 84..92: Method [definition]
|
|
||||||
"self" @ 93..97: SelfParameter [definition]
|
|
||||||
"\"\"\"unrelated string\"\"\"" @ 110..132: String
|
|
||||||
"x" @ 138..139: Variable [definition]
|
|
||||||
"str" @ 141..144: Class
|
|
||||||
"\"hello\"" @ 147..154: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn module_docstring_classification() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
"""Example module
|
|
||||||
|
|
||||||
What a good module wooo
|
|
||||||
"""
|
|
||||||
|
|
||||||
def my_func(): pass
|
|
||||||
|
|
||||||
"""unrelated string"""
|
|
||||||
|
|
||||||
x: str = "hello"
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"\"\"\"Example module\n\nWhat a good module wooo\n\"\"\"" @ 1..47: String [documentation]
|
|
||||||
"my_func" @ 53..60: Function [definition]
|
|
||||||
"\"\"\"unrelated string\"\"\"" @ 70..92: String
|
|
||||||
"x" @ 94..95: Variable [definition]
|
|
||||||
"str" @ 97..100: Class
|
|
||||||
"\"hello\"" @ 103..110: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn attribute_docstring_classification() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
important_value: str = "wow"
|
|
||||||
"""This is the most important value
|
|
||||||
|
|
||||||
Don't trust the other guy
|
|
||||||
"""
|
|
||||||
|
|
||||||
x = "unrelated string"
|
|
||||||
|
|
||||||
other_value: int = 2
|
|
||||||
"""This is such an import value omg
|
|
||||||
|
|
||||||
Trust me
|
|
||||||
"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"important_value" @ 1..16: Variable [definition]
|
|
||||||
"str" @ 18..21: Class
|
|
||||||
"\"wow\"" @ 24..29: String
|
|
||||||
"\"\"\"This is the most important value\n\nDon't trust the other guy\n\"\"\"" @ 30..96: String [documentation]
|
|
||||||
"x" @ 98..99: Variable [definition]
|
|
||||||
"\"unrelated string\"" @ 102..120: String
|
|
||||||
"other_value" @ 122..133: Variable [definition]
|
|
||||||
"int" @ 135..138: Class
|
|
||||||
"2" @ 141..142: Number
|
|
||||||
"\"\"\"This is such an import value omg\n\nTrust me\n\"\"\"" @ 143..192: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn attribute_docstring_classification_spill() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
if True:
|
|
||||||
x = 1
|
|
||||||
"this shouldn't be a docstring but also it doesn't matter much"
|
|
||||||
"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"True" @ 4..8: BuiltinConstant
|
|
||||||
"x" @ 14..15: Variable [definition]
|
|
||||||
"1" @ 18..19: Number
|
|
||||||
"\"this shouldn't be a docstring but also it doesn't matter much\"" @ 20..83: String [documentation]
|
|
||||||
"\"\"\"\n" @ 84..88: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"""wow cool docs""" """and docs"""
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
"""wow cool docs""" """and docs"""
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
"""wow cool docs""" """and docs"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 20..39: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 40..54: String [documentation]
|
|
||||||
"my_func" @ 60..67: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 75..94: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 95..109: String [documentation]
|
|
||||||
"x" @ 111..112: Variable [definition]
|
|
||||||
"1" @ 115..116: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 117..136: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 137..151: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat_parens() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 30..49: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 58..72: String [documentation]
|
|
||||||
"my_func" @ 84..91: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 109..128: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 137..151: String [documentation]
|
|
||||||
"x" @ 159..160: Variable [definition]
|
|
||||||
"1" @ 163..164: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 171..190: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 195..209: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat_parens_commented_nextline() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
(
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 30..49: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 109..123: String [documentation]
|
|
||||||
"my_func" @ 135..142: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 160..179: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 239..253: String [documentation]
|
|
||||||
"x" @ 261..262: Variable [definition]
|
|
||||||
"1" @ 265..266: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 273..292: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 344..358: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat_commented_nextline() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
"""wow cool docs"""
|
|
||||||
# and a comment that shouldn't be included
|
|
||||||
"""and docs"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 20..39: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 91..105: String
|
|
||||||
"my_func" @ 111..118: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 126..145: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 197..211: String
|
|
||||||
"x" @ 213..214: Variable [definition]
|
|
||||||
"1" @ 217..218: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 219..238: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 282..296: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat_commented_sameline() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"""wow cool docs""" # and a comment
|
|
||||||
"""and docs""" # that shouldn't be included
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
"""wow cool docs""" # and a comment
|
|
||||||
"""and docs""" # that shouldn't be included
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
"""wow cool docs""" # and a comment
|
|
||||||
"""and docs""" # that shouldn't be included
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 20..39: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 60..74: String
|
|
||||||
"my_func" @ 114..121: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 129..148: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 169..183: String
|
|
||||||
"x" @ 219..220: Variable [definition]
|
|
||||||
"1" @ 223..224: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 225..244: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 261..275: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_concat_slashed() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"""wow cool docs""" \
|
|
||||||
"""and docs"""
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
"""wow cool docs""" \
|
|
||||||
"""and docs"""
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
"""wow cool docs""" \
|
|
||||||
"""and docs"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 20..39: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 46..60: String [documentation]
|
|
||||||
"my_func" @ 66..73: Function [definition]
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 81..100: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 107..121: String [documentation]
|
|
||||||
"x" @ 123..124: Variable [definition]
|
|
||||||
"1" @ 127..128: Number
|
|
||||||
"\"\"\"wow cool docs\"\"\"" @ 129..148: String [documentation]
|
|
||||||
"\"\"\"and docs\"\"\"" @ 151..165: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn docstring_classification_plus() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
"wow cool docs" + "and docs"
|
|
||||||
|
|
||||||
def my_func():
|
|
||||||
"wow cool docs" + "and docs"
|
|
||||||
|
|
||||||
x = 1
|
|
||||||
"wow cool docs" + "and docs"
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"\"wow cool docs\"" @ 20..35: String
|
|
||||||
"\"and docs\"" @ 38..48: String
|
|
||||||
"my_func" @ 54..61: Function [definition]
|
|
||||||
"\"wow cool docs\"" @ 69..84: String
|
|
||||||
"\"and docs\"" @ 87..97: String
|
|
||||||
"x" @ 99..100: Variable [definition]
|
|
||||||
"1" @ 103..104: Number
|
|
||||||
"\"wow cool docs\"" @ 105..120: String
|
|
||||||
"\"and docs\"" @ 123..133: String
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn class_attribute_docstring_classification() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
class MyClass:
|
|
||||||
important_value: str = "wow"
|
|
||||||
"""This is the most important value
|
|
||||||
|
|
||||||
Don't trust the other guy
|
|
||||||
"""
|
|
||||||
|
|
||||||
x = "unrelated string"
|
|
||||||
|
|
||||||
other_value: int = 2
|
|
||||||
"""This is such an import value omg
|
|
||||||
|
|
||||||
Trust me
|
|
||||||
"""
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"MyClass" @ 7..14: Class [definition]
|
|
||||||
"important_value" @ 20..35: Variable [definition]
|
|
||||||
"str" @ 37..40: Class
|
|
||||||
"\"wow\"" @ 43..48: String
|
|
||||||
"\"\"\"This is the most important value\n\n Don't trust the other guy\n \"\"\"" @ 53..127: String [documentation]
|
|
||||||
"x" @ 133..134: Variable [definition]
|
|
||||||
"\"unrelated string\"" @ 137..155: String
|
|
||||||
"other_value" @ 161..172: Variable [definition]
|
|
||||||
"int" @ 174..177: Class
|
|
||||||
"2" @ 180..181: Number
|
|
||||||
"\"\"\"This is such an import value omg\n\n Trust me\n \"\"\"" @ 186..243: String [documentation]
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug_int_classification() {
|
fn test_debug_int_classification() {
|
||||||
let test = SemanticTokenTest::new(
|
let test = SemanticTokenTest::new(
|
||||||
@@ -2712,49 +2230,6 @@ class MyClass:
|
|||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_constant_variations() {
|
|
||||||
let test = SemanticTokenTest::new(
|
|
||||||
r#"
|
|
||||||
A = 1
|
|
||||||
AB = 1
|
|
||||||
ABC = 1
|
|
||||||
A1 = 1
|
|
||||||
AB1 = 1
|
|
||||||
ABC1 = 1
|
|
||||||
A_B = 1
|
|
||||||
A1_B = 1
|
|
||||||
A_B1 = 1
|
|
||||||
A_1 = 1
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tokens = test.highlight_file();
|
|
||||||
|
|
||||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
|
||||||
"A" @ 1..2: Variable [definition]
|
|
||||||
"1" @ 5..6: Number
|
|
||||||
"AB" @ 7..9: Variable [definition, readonly]
|
|
||||||
"1" @ 12..13: Number
|
|
||||||
"ABC" @ 14..17: Variable [definition, readonly]
|
|
||||||
"1" @ 20..21: Number
|
|
||||||
"A1" @ 22..24: Variable [definition, readonly]
|
|
||||||
"1" @ 27..28: Number
|
|
||||||
"AB1" @ 29..32: Variable [definition, readonly]
|
|
||||||
"1" @ 35..36: Number
|
|
||||||
"ABC1" @ 37..41: Variable [definition, readonly]
|
|
||||||
"1" @ 44..45: Number
|
|
||||||
"A_B" @ 46..49: Variable [definition, readonly]
|
|
||||||
"1" @ 52..53: Number
|
|
||||||
"A1_B" @ 54..58: Variable [definition, readonly]
|
|
||||||
"1" @ 61..62: Number
|
|
||||||
"A_B1" @ 63..67: Variable [definition, readonly]
|
|
||||||
"1" @ 70..71: Number
|
|
||||||
"A_1" @ 72..75: Variable [definition, readonly]
|
|
||||||
"1" @ 78..79: Number
|
|
||||||
"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_implicitly_concatenated_strings() {
|
fn test_implicitly_concatenated_strings() {
|
||||||
let test = SemanticTokenTest::new(
|
let test = SemanticTokenTest::new(
|
||||||
@@ -3328,12 +2803,6 @@ def foo(self, **key, value=10):
|
|||||||
if token.modifiers.contains(SemanticTokenModifier::ASYNC) {
|
if token.modifiers.contains(SemanticTokenModifier::ASYNC) {
|
||||||
mods.push("async");
|
mods.push("async");
|
||||||
}
|
}
|
||||||
if token
|
|
||||||
.modifiers
|
|
||||||
.contains(SemanticTokenModifier::DOCUMENTATION)
|
|
||||||
{
|
|
||||||
mods.push("documentation");
|
|
||||||
}
|
|
||||||
format!(" [{}]", mods.join(", "))
|
format!(" [{}]", mods.join(", "))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,11 @@
|
|||||||
//! types, and documentation. It supports multiple signatures for union types
|
//! types, and documentation. It supports multiple signatures for union types
|
||||||
//! and overloads.
|
//! and overloads.
|
||||||
|
|
||||||
use crate::Db;
|
|
||||||
use crate::docstring::Docstring;
|
use crate::docstring::Docstring;
|
||||||
use crate::goto::Definitions;
|
use crate::goto::Definitions;
|
||||||
|
use crate::{Db, find_node::covering_node};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::find_node::covering_node;
|
|
||||||
use ruff_python_ast::token::TokenKind;
|
use ruff_python_ast::token::TokenKind;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
@@ -125,11 +124,6 @@ fn get_call_expr(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Find the covering node at the given position that is a function call.
|
// Find the covering node at the given position that is a function call.
|
||||||
// Note that we are okay with the range being anywhere within a call
|
|
||||||
// expression, even if it's not in the arguments portion of the call
|
|
||||||
// expression. This is because, e.g., a user can request signature
|
|
||||||
// information at a call site, and this should ideally work anywhere
|
|
||||||
// within the call site, even at the function name.
|
|
||||||
let call = covering_node(root_node, token.range())
|
let call = covering_node(root_node, token.range())
|
||||||
.find_first(|node| {
|
.find_first(|node| {
|
||||||
if !node.is_expr_call() {
|
if !node.is_expr_call() {
|
||||||
|
|||||||
@@ -115,10 +115,6 @@ pub struct Project {
|
|||||||
|
|
||||||
#[default]
|
#[default]
|
||||||
verbose_flag: bool,
|
verbose_flag: bool,
|
||||||
|
|
||||||
/// Whether to enforce exclusion rules even to files explicitly passed to ty on the command line.
|
|
||||||
#[default]
|
|
||||||
force_exclude_flag: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A progress reporter.
|
/// A progress reporter.
|
||||||
@@ -384,16 +380,6 @@ impl Project {
|
|||||||
self.verbose_flag(db)
|
self.verbose_flag(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_force_exclude(self, db: &mut dyn Db, force: bool) {
|
|
||||||
if self.force_exclude_flag(db) != force {
|
|
||||||
self.set_force_exclude_flag(db).to(force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_exclude(self, db: &dyn Db) -> bool {
|
|
||||||
self.force_exclude_flag(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the paths that should be checked.
|
/// Returns the paths that should be checked.
|
||||||
///
|
///
|
||||||
/// The default is to check the entire project in which case this method returns
|
/// The default is to check the entire project in which case this method returns
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use ruff_python_ast::name::Name;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use ty_combine::Combine;
|
use ty_combine::Combine;
|
||||||
use ty_python_semantic::{MisconfigurationMode, ProgramSettings};
|
use ty_python_semantic::ProgramSettings;
|
||||||
|
|
||||||
use crate::metadata::options::ProjectOptionsOverrides;
|
use crate::metadata::options::ProjectOptionsOverrides;
|
||||||
use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError};
|
use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError};
|
||||||
@@ -37,9 +37,6 @@ pub struct ProjectMetadata {
|
|||||||
/// The path ordering doesn't imply precedence.
|
/// The path ordering doesn't imply precedence.
|
||||||
#[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))]
|
#[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))]
|
||||||
pub(super) extra_configuration_paths: Vec<SystemPathBuf>,
|
pub(super) extra_configuration_paths: Vec<SystemPathBuf>,
|
||||||
|
|
||||||
#[cfg_attr(test, serde(skip))]
|
|
||||||
pub(super) misconfiguration_mode: MisconfigurationMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectMetadata {
|
impl ProjectMetadata {
|
||||||
@@ -50,7 +47,6 @@ impl ProjectMetadata {
|
|||||||
root,
|
root,
|
||||||
extra_configuration_paths: Vec::default(),
|
extra_configuration_paths: Vec::default(),
|
||||||
options: Options::default(),
|
options: Options::default(),
|
||||||
misconfiguration_mode: MisconfigurationMode::Fail,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +70,6 @@ impl ProjectMetadata {
|
|||||||
root: system.current_directory().to_path_buf(),
|
root: system.current_directory().to_path_buf(),
|
||||||
options,
|
options,
|
||||||
extra_configuration_paths: vec![path],
|
extra_configuration_paths: vec![path],
|
||||||
misconfiguration_mode: MisconfigurationMode::Fail,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +82,6 @@ impl ProjectMetadata {
|
|||||||
pyproject.tool.and_then(|tool| tool.ty).unwrap_or_default(),
|
pyproject.tool.and_then(|tool| tool.ty).unwrap_or_default(),
|
||||||
root,
|
root,
|
||||||
pyproject.project.as_ref(),
|
pyproject.project.as_ref(),
|
||||||
MisconfigurationMode::Fail,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +90,6 @@ impl ProjectMetadata {
|
|||||||
mut options: Options,
|
mut options: Options,
|
||||||
root: SystemPathBuf,
|
root: SystemPathBuf,
|
||||||
project: Option<&Project>,
|
project: Option<&Project>,
|
||||||
misconfiguration_mode: MisconfigurationMode,
|
|
||||||
) -> Result<Self, ResolveRequiresPythonError> {
|
) -> Result<Self, ResolveRequiresPythonError> {
|
||||||
let name = project
|
let name = project
|
||||||
.and_then(|project| project.name.as_deref())
|
.and_then(|project| project.name.as_deref())
|
||||||
@@ -124,7 +117,6 @@ impl ProjectMetadata {
|
|||||||
root,
|
root,
|
||||||
options,
|
options,
|
||||||
extra_configuration_paths: Vec::new(),
|
extra_configuration_paths: Vec::new(),
|
||||||
misconfiguration_mode,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +194,6 @@ impl ProjectMetadata {
|
|||||||
pyproject
|
pyproject
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|pyproject| pyproject.project.as_ref()),
|
.and_then(|pyproject| pyproject.project.as_ref()),
|
||||||
MisconfigurationMode::Fail,
|
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
ProjectMetadataError::InvalidRequiresPythonConstraint {
|
ProjectMetadataError::InvalidRequiresPythonConstraint {
|
||||||
@@ -282,13 +273,8 @@ impl ProjectMetadata {
|
|||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
vendored: &VendoredFileSystem,
|
vendored: &VendoredFileSystem,
|
||||||
) -> anyhow::Result<ProgramSettings> {
|
) -> anyhow::Result<ProgramSettings> {
|
||||||
self.options.to_program_settings(
|
self.options
|
||||||
self.root(),
|
.to_program_settings(self.root(), self.name(), system, vendored)
|
||||||
self.name(),
|
|
||||||
system,
|
|
||||||
vendored,
|
|
||||||
self.misconfiguration_mode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_overrides(&mut self, overrides: &ProjectOptionsOverrides) {
|
pub fn apply_overrides(&mut self, overrides: &ProjectOptionsOverrides) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user