Compare commits
85 Commits
0.13.1
...
dcreager/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa4a038b02 | ||
|
|
209ae1a39b | ||
|
|
8847e5e665 | ||
|
|
239dda8a05 | ||
|
|
ef2f49dcf1 | ||
|
|
3932f7c849 | ||
|
|
2af8c53110 | ||
|
|
0bae7e613d | ||
|
|
02ebb2ee61 | ||
|
|
e66a872c14 | ||
|
|
589a674a8d | ||
|
|
e4ac9e9041 | ||
|
|
f2b7c82534 | ||
|
|
cfc64d1707 | ||
|
|
6b7a9dc2f2 | ||
|
|
9903104328 | ||
|
|
beec2f2dbb | ||
|
|
c256c7943c | ||
|
|
35ed55ec8c | ||
|
|
b0bdf0334e | ||
|
|
7331d393c5 | ||
|
|
529e5fa6c2 | ||
|
|
efbb80f747 | ||
|
|
9f3cffc65c | ||
|
|
21be94ac33 | ||
|
|
b7d5dc98c1 | ||
|
|
e1bb74b25a | ||
|
|
edeb45804e | ||
|
|
bea92c8229 | ||
|
|
f2cc2f604f | ||
|
|
c361e2f759 | ||
|
|
0e83af0b80 | ||
|
|
e6073d0cca | ||
|
|
73b4b1ed17 | ||
|
|
83f80effec | ||
|
|
fcc76bb7b2 | ||
|
|
eea87e24e3 | ||
|
|
3e1e02e9b6 | ||
|
|
09f570af92 | ||
|
|
722f1a7d7a | ||
|
|
dbc5983503 | ||
|
|
46decd4feb | ||
|
|
bf38e69870 | ||
|
|
4ed8c65d29 | ||
|
|
2c916562ba | ||
|
|
edb920b4d5 | ||
|
|
346842f003 | ||
|
|
742f8a4ee6 | ||
|
|
fd5c48c539 | ||
|
|
ef4df34652 | ||
|
|
036f3616a1 | ||
|
|
68ae9c8a15 | ||
|
|
094bf70a60 | ||
|
|
32d00cd569 | ||
|
|
00a9e65d00 | ||
|
|
0c7cfd2a8d | ||
|
|
61bb2a8245 | ||
|
|
f1aacd0f2c | ||
|
|
3033d1e5a5 | ||
|
|
d96d40ef42 | ||
|
|
79224cc53d | ||
|
|
fe01a5e032 | ||
|
|
740425d39d | ||
|
|
4fddd373aa | ||
|
|
0b1e12f086 | ||
|
|
d12324f06e | ||
|
|
2c6c3e78f6 | ||
|
|
3ffe56d19d | ||
|
|
eb354608d2 | ||
|
|
12086dfa69 | ||
|
|
5f294f9f2e | ||
|
|
44fc87f491 | ||
|
|
43cda2dfe9 | ||
|
|
bd5b3e4f6e | ||
|
|
3bf4dae452 | ||
|
|
8eeca023d6 | ||
|
|
c94ddb590f | ||
|
|
bae8ddfb8a | ||
|
|
c0fb235a70 | ||
|
|
b5a3503a58 | ||
|
|
d45209f425 | ||
|
|
5d1cd85662 | ||
|
|
902b0b4ce9 | ||
|
|
6f2b60708e | ||
|
|
bc89d0394c |
45
.github/workflows/ci.yaml
vendored
@@ -88,7 +88,6 @@ jobs:
|
||||
':!crates/ruff_python_formatter/**' \
|
||||
':!crates/ruff_formatter/**' \
|
||||
':!crates/ruff_dev/**' \
|
||||
':!crates/ruff_db/**' \
|
||||
':scripts/*' \
|
||||
':python/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
@@ -907,10 +906,13 @@ jobs:
|
||||
run: npm run fmt:check
|
||||
working-directory: playground
|
||||
|
||||
benchmarks-instrumented:
|
||||
benchmarks-instrumented-ruff:
|
||||
name: "benchmarks instrumented (ruff)"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
(needs.determine_changes.outputs.formatter == 'true' || needs.determine_changes.outputs.linter == 'true')
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
@@ -930,7 +932,42 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark formatter lexer linter parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
benchmarks-instrumented-ty:
|
||||
name: "benchmarks instrumented (ty)"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
|
||||
16
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -95,6 +95,14 @@ jobs:
|
||||
--new-name "$REF_NAME" \
|
||||
--output diff-statistics.md
|
||||
|
||||
ecosystem-analyzer \
|
||||
generate-timing-diff \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output-html dist/timing.html
|
||||
|
||||
echo '## `ecosystem-analyzer` results' > comment.md
|
||||
echo >> comment.md
|
||||
cat diff-statistics.md >> comment.md
|
||||
@@ -118,7 +126,7 @@ jobs:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)**" >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
|
||||
|
||||
- name: Upload comment
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
@@ -137,3 +145,9 @@ jobs:
|
||||
with:
|
||||
name: diff.html
|
||||
path: dist/diff.html
|
||||
|
||||
- name: Upload timing diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: timing.html
|
||||
path: dist/timing.html
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@27dd66d9e397d986ef9c631119ee09556eab8af9"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@fc0f612798710b0dd69bb7528bc9b361dc60bd43"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
|
||||
5
.vscode/settings.json
vendored
@@ -3,4 +3,7 @@
|
||||
"--all-features"
|
||||
],
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
}
|
||||
"search.exclude": {
|
||||
"**/*.snap": true
|
||||
}
|
||||
}
|
||||
|
||||
48
CHANGELOG.md
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## 0.13.2
|
||||
|
||||
Released on 2025-09-25.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-async`\] Implement `blocking-path-method` (`ASYNC240`) ([#20264](https://github.com/astral-sh/ruff/pull/20264))
|
||||
- \[`flake8-bugbear`\] Implement `map-without-explicit-strict` (`B912`) ([#20429](https://github.com/astral-sh/ruff/pull/20429))
|
||||
- \[`flake8-bultins`\] Detect class-scope builtin shadowing in decorators, default args, and attribute initializers (`A003`) ([#20178](https://github.com/astral-sh/ruff/pull/20178))
|
||||
- \[`ruff`\] Implement `logging-eager-conversion` (`RUF065`) ([#19942](https://github.com/astral-sh/ruff/pull/19942))
|
||||
- Include `.pyw` files by default when linting and formatting ([#20458](https://github.com/astral-sh/ruff/pull/20458))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Deduplicate input paths ([#20105](https://github.com/astral-sh/ruff/pull/20105))
|
||||
- \[`flake8-comprehensions`\] Preserve trailing commas for single-element lists (`C409`) ([#19571](https://github.com/astral-sh/ruff/pull/19571))
|
||||
- \[`flake8-pyi`\] Avoid syntax error from conflict with `PIE790` (`PYI021`) ([#20010](https://github.com/astral-sh/ruff/pull/20010))
|
||||
- \[`flake8-simplify`\] Correct fix for positive `maxsplit` without separator (`SIM905`) ([#20056](https://github.com/astral-sh/ruff/pull/20056))
|
||||
- \[`pyupgrade`\] Fix `UP008` not to apply when `__class__` is a local variable ([#20497](https://github.com/astral-sh/ruff/pull/20497))
|
||||
- \[`ruff`\] Fix `B004` to skip invalid `hasattr`/`getattr` calls ([#20486](https://github.com/astral-sh/ruff/pull/20486))
|
||||
- \[`ruff`\] Replace `-nan` with `nan` when using the value to construct a `Decimal` (`FURB164` ) ([#20391](https://github.com/astral-sh/ruff/pull/20391))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add 'Finding ways to help' to CONTRIBUTING.md ([#20567](https://github.com/astral-sh/ruff/pull/20567))
|
||||
- Update import path to `ruff-wasm-web` ([#20539](https://github.com/astral-sh/ruff/pull/20539))
|
||||
- \[`flake8-bandit`\] Clarify the supported hashing functions (`S324`) ([#20534](https://github.com/astral-sh/ruff/pull/20534))
|
||||
|
||||
### Other changes
|
||||
|
||||
- \[`playground`\] Allow hover quick fixes to appear for overlapping diagnostics ([#20527](https://github.com/astral-sh/ruff/pull/20527))
|
||||
- \[`playground`\] Fix non‑BMP code point handling in quick fixes and markers ([#20526](https://github.com/astral-sh/ruff/pull/20526))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@BurntSushi](https://github.com/BurntSushi)
|
||||
- [@mtshiba](https://github.com/mtshiba)
|
||||
- [@second-ed](https://github.com/second-ed)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@ShikChen](https://github.com/ShikChen)
|
||||
- [@PieterCK](https://github.com/PieterCK)
|
||||
- [@GDYendell](https://github.com/GDYendell)
|
||||
- [@RazerM](https://github.com/RazerM)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@amyreese](https://github.com/amyreese)
|
||||
- [@ntbre](https://github.com/ntBre)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
|
||||
## 0.13.1
|
||||
|
||||
Released on 2025-09-18.
|
||||
|
||||
@@ -7,21 +7,35 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing
|
||||
> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
## Finding ways to help
|
||||
|
||||
Ruff welcomes contributions in the form of pull requests.
|
||||
We label issues that would be good for a first time contributor as
|
||||
[`good first issue`](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
These usually do not require significant experience with Rust or the Ruff code base.
|
||||
|
||||
For small changes (e.g., bug fixes), feel free to submit a PR.
|
||||
We label issues that we think are a good opportunity for subsequent contributions as
|
||||
[`help wanted`](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
|
||||
These require varying levels of experience with Rust and Ruff. Often, we want to accomplish these
|
||||
tasks but do not have the resources to do so ourselves.
|
||||
|
||||
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
|
||||
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
|
||||
You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the
|
||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
|
||||
that are ready for contributions.
|
||||
You don't need our permission to start on an issue we have labeled as appropriate for community
|
||||
contribution as described above. However, it's a good idea to indicate that you are going to work on
|
||||
an issue to avoid concurrent attempts to solve the same problem.
|
||||
|
||||
If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)!
|
||||
Please check in with us before starting work on an issue that has not been labeled as appropriate
|
||||
for community contribution. We're happy to receive contributions for other issues, but it's
|
||||
important to make sure we have consensus on the solution to the problem first.
|
||||
|
||||
Outside of issues with the labels above, issues labeled as
|
||||
[`bug`](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3A%22bug%22) are the
|
||||
best candidates for contribution. In contrast, issues labeled with `needs-decision` or
|
||||
`needs-design` are _not_ good candidates for contribution. Please do not open pull requests for
|
||||
issues with these labels.
|
||||
|
||||
Please do not open pull requests for new features without prior discussion. While we appreciate
|
||||
exploration of new features, we will often close these pull requests immediately. Adding a
|
||||
new feature to ruff creates a long-term maintenance burden and requires strong consensus from the ruff
|
||||
team before it is appropriate to begin work on an implementation.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
||||
382
Cargo.lock
generated
@@ -23,12 +23,6 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -104,9 +98,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-svg"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc03a770ef506fe1396c0e476120ac0e6523cf14b74218dd5f18cd6833326fa9"
|
||||
checksum = "26b9ec8c976eada1b0f9747a3d7cc4eae3bef10613e443746e7487f26c872fde"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
@@ -128,9 +122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -284,9 +278,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "boxcar"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa"
|
||||
checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -346,10 +340,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.31"
|
||||
version = "1.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
@@ -357,9 +352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
@@ -369,14 +364,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -408,9 +402,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +412,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -431,9 +425,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -650,15 +644,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
|
||||
checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.1",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -837,9 +831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -847,9 +841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@@ -861,9 +855,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@@ -956,7 +950,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1037,12 +1031,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1053,12 +1047,11 @@ checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6"
|
||||
|
||||
[[package]]
|
||||
name = "escargot"
|
||||
version = "0.5.14"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83f351750780493fc33fa0ce8ba3c7d61f9736cfa3b3bb9ee2342643ffe40211"
|
||||
checksum = "11c3aea32bc97b500c9ca6a72b768a26e558264303d101d3409cf6d57a9ed0cf"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1101,6 +1094,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
@@ -1168,9 +1167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a17a226478b2e8294ded60782c03efe54476aa8cd1371d0e5ad9d1071e74e0"
|
||||
checksum = "e3814abc7da8ab18d2fd820f5b540b5e39b6af0a32de1bdd7c47576693074843"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1179,21 +1178,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5697765925a05c9d401dd04a93dfd662d336cc25fdcc3301220385a1ffcfdde5"
|
||||
checksum = "5dfe2cec5b5ce8fb94dcdb16a1708baa4d0609cc3ce305ca5d3f6f2ffb59baed"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.23"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
||||
dependencies = [
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -1219,7 +1218,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1280,6 +1279,15 @@ dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
@@ -1321,9 +1329,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -1493,13 +1501,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.1"
|
||||
version = "2.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1508,7 +1517,7 @@ version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
|
||||
dependencies = [
|
||||
"console 0.16.0",
|
||||
"console 0.16.1",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.1",
|
||||
"unit-prefix",
|
||||
@@ -1588,9 +1597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.20"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
|
||||
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
@@ -1719,9 +1728,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
@@ -1735,9 +1744,9 @@ checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -1812,9 +1821,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
@@ -1835,9 +1844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
@@ -2133,12 +2142,13 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.10"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dcd63f1ae4b091e314a26627c467dd8810d674ba798abc0e566679955776c63"
|
||||
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2289,9 +2299,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.16",
|
||||
@@ -2300,9 +2310,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
||||
checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -2310,9 +2320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
||||
checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@@ -2323,9 +2333,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
@@ -2398,9 +2408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
|
||||
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@@ -2453,9 +2463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
@@ -2705,15 +2715,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
@@ -2728,7 +2738,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2984,7 +2994,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -2994,7 +3004,7 @@ dependencies = [
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"imperative",
|
||||
"insta",
|
||||
"is-macro",
|
||||
@@ -3338,7 +3348,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3427,22 +3437,22 @@ checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@@ -3453,7 +3463,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3477,12 +3487,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3537,9 +3547,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@@ -3558,18 +3568,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3613,11 +3623,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3631,9 +3641,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.14.0"
|
||||
version = "3.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
|
||||
checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -3642,9 +3652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.14.0"
|
||||
version = "3.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
||||
checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -3830,7 +3840,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3844,12 +3854,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4009,9 +4019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
@@ -4024,14 +4034,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.5"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
@@ -4039,44 +4049,39 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -4286,6 +4291,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4303,7 +4309,7 @@ dependencies = [
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
@@ -4513,9 +4519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||
checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -4740,18 +4746,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -4762,9 +4777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -4776,9 +4791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.51"
|
||||
version = "0.4.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
|
||||
checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -4789,9 +4804,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4799,9 +4814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4812,18 +4827,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80cc7f8a4114fdaa0c58383caf973fc126cf004eba25c9dc639bccd3880d55ad"
|
||||
checksum = "aee0a0f5343de9221a0d233b04520ed8dc2e6728dce180b1dcd9288ec9d9fa3c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"minicov",
|
||||
@@ -4834,9 +4849,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ada2ab788d46d4bda04c9d567702a79c8ced14f51f221646a16ed39d0e6a5d"
|
||||
checksum = "a369369e4360c2884c3168d22bded735c43cccae97bbc147586d4b480edd138d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4845,9 +4860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
||||
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4885,22 +4900,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -4941,20 +4956,20 @@ checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5124,9 +5139,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5138,13 +5153,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
@@ -5193,18 +5205,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5299,9 +5311,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
@@ -86,7 +86,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.6.2", features = [
|
||||
get-size2 = { version = "0.7.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -95,7 +95,7 @@ get-size2 = { version = "0.6.2", features = [
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
globwalk = { version = "0.9.1" }
|
||||
hashbrown = { version = "0.15.0", default-features = false, features = [
|
||||
hashbrown = { version = "0.16.0", default-features = false, features = [
|
||||
"raw-entry",
|
||||
"equivalent",
|
||||
"inline-more",
|
||||
@@ -144,7 +144,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "3713cd7eb30821c0c086591832dd6f59f2af7fe7", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "29ab321b45d00daa4315fa2a06f7207759a8c87e", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
@@ -203,7 +203,7 @@ wild = { version = "2" }
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid"]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2"]
|
||||
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.13.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.13.1/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.13.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.13.2/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.13.1
|
||||
rev: v0.13.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -500,6 +500,35 @@ OTHER = "OTHER"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/20035>
|
||||
#[test]
|
||||
fn deduplicate_directory_and_explicit_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root = tempdir.path();
|
||||
|
||||
let main = root.join("main.py");
|
||||
fs::write(&main, "x = 1\n")?;
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(root)
|
||||
.args(["format", "--no-cache", "--check"])
|
||||
.arg(".")
|
||||
.arg("main.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: main.py
|
||||
1 file would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_error() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -271,6 +271,50 @@ OTHER = "OTHER"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/20035>
|
||||
#[test]
|
||||
fn deduplicate_directory_and_explicit_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root = tempdir.path();
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
exclude = ["main.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let main = root.join("main.py");
|
||||
fs::write(&main, "import os\n")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(root)
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.arg(".")
|
||||
// Explicitly pass main.py, should be linted regardless of it being excluded by lint.exclude
|
||||
.arg("main.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -2401,6 +2445,7 @@ requires-python = ">= 3.11"
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -2432,6 +2477,320 @@ requires-python = ">= 3.11"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// tmp
|
||||
/// ├── pyproject.toml #<--- no `[tool.ruff]`
|
||||
/// └── test.py
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_python_no_tool_preview_enabled() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let ruff_toml = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"[project]
|
||||
requires-python = ">= 3.11"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let testpy = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&testpy,
|
||||
r#"from typing import Union;foo: Union[int, str] = 1"#,
|
||||
)?;
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&project_dir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--preview")
|
||||
.arg("--show-settings")
|
||||
.args(["--select","UP007"])
|
||||
.arg("test.py")
|
||||
.arg("-")
|
||||
.current_dir(project_dir)
|
||||
, @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Resolved settings for: "[TMP]/test.py"
|
||||
|
||||
# General Settings
|
||||
cache_dir = "[TMP]/.ruff_cache"
|
||||
fix = false
|
||||
fix_only = false
|
||||
output_format = concise
|
||||
show_fixes = false
|
||||
unsafe_fixes = hint
|
||||
|
||||
# File Resolver Settings
|
||||
file_resolver.exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".ipynb_checkpoints",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pyenv",
|
||||
".pytest_cache",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
".vscode",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
"venv",
|
||||
]
|
||||
file_resolver.extend_exclude = []
|
||||
file_resolver.force_exclude = false
|
||||
file_resolver.include = [
|
||||
"*.py",
|
||||
"*.pyi",
|
||||
"*.pyw",
|
||||
"*.ipynb",
|
||||
"**/pyproject.toml",
|
||||
]
|
||||
file_resolver.extend_include = []
|
||||
file_resolver.respect_gitignore = true
|
||||
file_resolver.project_root = "[TMP]/"
|
||||
|
||||
# Linter Settings
|
||||
linter.exclude = []
|
||||
linter.project_root = "[TMP]/"
|
||||
linter.rules.enabled = [
|
||||
non-pep604-annotation-union (UP007),
|
||||
]
|
||||
linter.rules.should_fix = [
|
||||
non-pep604-annotation-union (UP007),
|
||||
]
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.unresolved_target_version = 3.11
|
||||
linter.per_file_target_version = {}
|
||||
linter.preview = enabled
|
||||
linter.explicit_preview_rules = false
|
||||
linter.extension = ExtensionMapping({})
|
||||
linter.allowed_confusables = []
|
||||
linter.builtins = []
|
||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||
linter.external = []
|
||||
linter.ignore_init_module_imports = true
|
||||
linter.logger_objects = []
|
||||
linter.namespace_packages = []
|
||||
linter.src = [
|
||||
"[TMP]/",
|
||||
"[TMP]/src",
|
||||
]
|
||||
linter.tab_size = 4
|
||||
linter.line_length = 88
|
||||
linter.task_tags = [
|
||||
TODO,
|
||||
FIXME,
|
||||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
linter.flake8_annotations.suppress_dummy_args = false
|
||||
linter.flake8_annotations.suppress_none_returning = false
|
||||
linter.flake8_annotations.allow_star_arg_any = false
|
||||
linter.flake8_annotations.ignore_fully_untyped = false
|
||||
linter.flake8_bandit.hardcoded_tmp_directory = [
|
||||
/tmp,
|
||||
/var/tmp,
|
||||
/dev/shm,
|
||||
]
|
||||
linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bandit.extend_markup_names = []
|
||||
linter.flake8_bandit.allowed_markup_calls = []
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.allowed_modules = []
|
||||
linter.flake8_builtins.ignorelist = []
|
||||
linter.flake8_builtins.strict_checking = false
|
||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
]
|
||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||
linter.flake8_import_conventions.aliases = {
|
||||
altair = alt,
|
||||
holoviews = hv,
|
||||
matplotlib = mpl,
|
||||
matplotlib.pyplot = plt,
|
||||
networkx = nx,
|
||||
numpy = np,
|
||||
numpy.typing = npt,
|
||||
pandas = pd,
|
||||
panel = pn,
|
||||
plotly.express = px,
|
||||
polars = pl,
|
||||
pyarrow = pa,
|
||||
seaborn = sns,
|
||||
tensorflow = tf,
|
||||
tkinter = tk,
|
||||
xml.etree.ElementTree = ET,
|
||||
}
|
||||
linter.flake8_import_conventions.banned_aliases = {}
|
||||
linter.flake8_import_conventions.banned_from = []
|
||||
linter.flake8_pytest_style.fixture_parentheses = false
|
||||
linter.flake8_pytest_style.parametrize_names_type = tuple
|
||||
linter.flake8_pytest_style.parametrize_values_type = list
|
||||
linter.flake8_pytest_style.parametrize_values_row_type = tuple
|
||||
linter.flake8_pytest_style.raises_require_match_for = [
|
||||
BaseException,
|
||||
Exception,
|
||||
ValueError,
|
||||
OSError,
|
||||
IOError,
|
||||
EnvironmentError,
|
||||
socket.error,
|
||||
]
|
||||
linter.flake8_pytest_style.raises_extend_require_match_for = []
|
||||
linter.flake8_pytest_style.mark_parentheses = false
|
||||
linter.flake8_quotes.inline_quotes = double
|
||||
linter.flake8_quotes.multiline_quotes = double
|
||||
linter.flake8_quotes.docstring_quotes = double
|
||||
linter.flake8_quotes.avoid_escape = true
|
||||
linter.flake8_self.ignore_names = [
|
||||
_make,
|
||||
_asdict,
|
||||
_replace,
|
||||
_fields,
|
||||
_field_defaults,
|
||||
_name_,
|
||||
_value_,
|
||||
]
|
||||
linter.flake8_tidy_imports.ban_relative_imports = "parents"
|
||||
linter.flake8_tidy_imports.banned_api = {}
|
||||
linter.flake8_tidy_imports.banned_module_level_imports = []
|
||||
linter.flake8_type_checking.strict = false
|
||||
linter.flake8_type_checking.exempt_modules = [
|
||||
typing,
|
||||
typing_extensions,
|
||||
]
|
||||
linter.flake8_type_checking.runtime_required_base_classes = []
|
||||
linter.flake8_type_checking.runtime_required_decorators = []
|
||||
linter.flake8_type_checking.quote_annotations = false
|
||||
linter.flake8_unused_arguments.ignore_variadic_names = false
|
||||
linter.isort.required_imports = []
|
||||
linter.isort.combine_as_imports = false
|
||||
linter.isort.force_single_line = false
|
||||
linter.isort.force_sort_within_sections = false
|
||||
linter.isort.detect_same_package = true
|
||||
linter.isort.case_sensitive = false
|
||||
linter.isort.force_wrap_aliases = false
|
||||
linter.isort.force_to_top = []
|
||||
linter.isort.known_modules = {}
|
||||
linter.isort.order_by_type = true
|
||||
linter.isort.relative_imports_order = furthest_to_closest
|
||||
linter.isort.single_line_exclusions = []
|
||||
linter.isort.split_on_trailing_comma = true
|
||||
linter.isort.classes = []
|
||||
linter.isort.constants = []
|
||||
linter.isort.variables = []
|
||||
linter.isort.no_lines_before = []
|
||||
linter.isort.lines_after_imports = -1
|
||||
linter.isort.lines_between_types = 0
|
||||
linter.isort.forced_separate = []
|
||||
linter.isort.section_order = [
|
||||
known { type = future },
|
||||
known { type = standard_library },
|
||||
known { type = third_party },
|
||||
known { type = first_party },
|
||||
known { type = local_folder },
|
||||
]
|
||||
linter.isort.default_section = known { type = third_party }
|
||||
linter.isort.no_sections = false
|
||||
linter.isort.from_first = false
|
||||
linter.isort.length_sort = false
|
||||
linter.isort.length_sort_straight = false
|
||||
linter.mccabe.max_complexity = 10
|
||||
linter.pep8_naming.ignore_names = [
|
||||
setUp,
|
||||
tearDown,
|
||||
setUpClass,
|
||||
tearDownClass,
|
||||
setUpModule,
|
||||
tearDownModule,
|
||||
asyncSetUp,
|
||||
asyncTearDown,
|
||||
setUpTestData,
|
||||
failureException,
|
||||
longMessage,
|
||||
maxDiff,
|
||||
]
|
||||
linter.pep8_naming.classmethod_decorators = []
|
||||
linter.pep8_naming.staticmethod_decorators = []
|
||||
linter.pycodestyle.max_line_length = 88
|
||||
linter.pycodestyle.max_doc_length = none
|
||||
linter.pycodestyle.ignore_overlong_task_comments = false
|
||||
linter.pyflakes.extend_generics = []
|
||||
linter.pyflakes.allowed_unused_imports = []
|
||||
linter.pylint.allow_magic_value_types = [
|
||||
str,
|
||||
bytes,
|
||||
]
|
||||
linter.pylint.allow_dunder_method_names = []
|
||||
linter.pylint.max_args = 5
|
||||
linter.pylint.max_positional_args = 5
|
||||
linter.pylint.max_returns = 6
|
||||
linter.pylint.max_bool_expr = 5
|
||||
linter.pylint.max_branches = 12
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
formatter.unresolved_target_version = 3.11
|
||||
formatter.per_file_target_version = {}
|
||||
formatter.preview = enabled
|
||||
formatter.line_width = 88
|
||||
formatter.line_ending = auto
|
||||
formatter.indent_style = space
|
||||
formatter.indent_width = 4
|
||||
formatter.quote_style = double
|
||||
formatter.magic_trailing_comma = respect
|
||||
formatter.docstring_code_format = disabled
|
||||
formatter.docstring_code_line_width = dynamic
|
||||
|
||||
# Analyze Settings
|
||||
analyze.exclude = []
|
||||
analyze.preview = enabled
|
||||
analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// tmp
|
||||
/// ├── pyproject.toml #<--- no `[tool.ruff]`
|
||||
@@ -2713,6 +3072,7 @@ requires-python = ">= 3.11"
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -3077,6 +3437,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -3457,6 +3818,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -3785,6 +4147,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -4113,6 +4476,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -4398,6 +4762,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -4736,6 +5101,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
@@ -6001,3 +6367,200 @@ fn rule_panic_mixed_results_full() -> Result<()> {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that the same rule fires across all supported extensions, but not on unsupported files
|
||||
#[test]
|
||||
fn supported_file_extensions() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let inner_dir = tempdir.path().join("src");
|
||||
fs::create_dir(&inner_dir)?;
|
||||
|
||||
// Create files of various types
|
||||
// text file
|
||||
fs::write(inner_dir.join("thing.txt"), b"hello world\n")?;
|
||||
// regular python
|
||||
fs::write(
|
||||
inner_dir.join("thing.py"),
|
||||
b"import os\nprint('hello world')\n",
|
||||
)?;
|
||||
// python typestub
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyi"),
|
||||
b"import os\nclass foo:\n ...\n",
|
||||
)?;
|
||||
// windows gui
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyw"),
|
||||
b"import os\nprint('hello world')\n",
|
||||
)?;
|
||||
// cython
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyx"),
|
||||
b"import os\ncdef int add(int a, int b):\n return a + b\n",
|
||||
)?;
|
||||
// notebook
|
||||
fs::write(
|
||||
inner_dir.join("thing.ipynb"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![
|
||||
(tempdir_filter(&tempdir).as_str(), "[TMP]/"),
|
||||
(r"\\", r"/"),
|
||||
]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "F401", "--output-format=concise", "--no-cache"])
|
||||
.args([inner_dir]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
[TMP]/src/thing.ipynb:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
[TMP]/src/thing.py:1:8: F401 [*] `os` imported but unused
|
||||
[TMP]/src/thing.pyi:1:8: F401 [*] `os` imported but unused
|
||||
Found 3 errors.
|
||||
[*] 3 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that the same rule fires across all supported extensions, but not on unsupported files
|
||||
#[test]
|
||||
fn supported_file_extensions_preview_enabled() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let inner_dir = tempdir.path().join("src");
|
||||
fs::create_dir(&inner_dir)?;
|
||||
|
||||
// Create files of various types
|
||||
// text file
|
||||
fs::write(inner_dir.join("thing.txt"), b"hello world\n")?;
|
||||
// regular python
|
||||
fs::write(
|
||||
inner_dir.join("thing.py"),
|
||||
b"import os\nprint('hello world')\n",
|
||||
)?;
|
||||
// python typestub
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyi"),
|
||||
b"import os\nclass foo:\n ...\n",
|
||||
)?;
|
||||
// windows gui
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyw"),
|
||||
b"import os\nprint('hello world')\n",
|
||||
)?;
|
||||
// cython
|
||||
fs::write(
|
||||
inner_dir.join("thing.pyx"),
|
||||
b"import os\ncdef int add(int a, int b):\n return a + b\n",
|
||||
)?;
|
||||
// notebook
|
||||
fs::write(
|
||||
inner_dir.join("thing.ipynb"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![
|
||||
(tempdir_filter(&tempdir).as_str(), "[TMP]/"),
|
||||
(r"\\", r"/"),
|
||||
]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "F401", "--preview", "--output-format=concise", "--no-cache"])
|
||||
.args([inner_dir]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
[TMP]/src/thing.ipynb:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
[TMP]/src/thing.py:1:8: F401 [*] `os` imported but unused
|
||||
[TMP]/src/thing.pyi:1:8: F401 [*] `os` imported but unused
|
||||
[TMP]/src/thing.pyw:1:8: F401 [*] `os` imported but unused
|
||||
Found 4 errors.
|
||||
[*] 4 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -371,6 +371,7 @@ linter.pylint.max_branches = 12
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1196px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1196px" height="164px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
500,
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::{
|
||||
Db,
|
||||
files::File,
|
||||
source::{SourceText, line_index, source_text},
|
||||
system::SystemPath,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -800,7 +799,7 @@ where
|
||||
T: Db,
|
||||
{
|
||||
fn path(&self, file: File) -> &str {
|
||||
relativize_path(self.system().current_directory(), file.path(self).as_str())
|
||||
file.path(self).as_str()
|
||||
}
|
||||
|
||||
fn input(&self, file: File) -> Input {
|
||||
@@ -836,7 +835,7 @@ where
|
||||
|
||||
impl FileResolver for &dyn Db {
|
||||
fn path(&self, file: File) -> &str {
|
||||
relativize_path(self.system().current_directory(), file.path(*self).as_str())
|
||||
file.path(*self).as_str()
|
||||
}
|
||||
|
||||
fn input(&self, file: File) -> Input {
|
||||
@@ -955,14 +954,6 @@ fn context_after(
|
||||
line
|
||||
}
|
||||
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
if let Ok(path) = SystemPath::new(path).strip_prefix(cwd) {
|
||||
return path.as_str();
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/// Given some source code and annotation ranges, this routine replaces
|
||||
/// unprintable characters with printable representations of them.
|
||||
///
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/azure.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
##vso[task.logissue type=error;sourcepath=fib.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused
|
||||
##vso[task.logissue type=error;sourcepath=fib.py;linenumber=6;columnnumber=5;code=F841;]Local variable `x` is assigned to but never used
|
||||
##vso[task.logissue type=error;sourcepath=undef.py;linenumber=1;columnnumber=4;code=F821;]Undefined name `a`
|
||||
##vso[task.logissue type=error;sourcepath=/fib.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused
|
||||
##vso[task.logissue type=error;sourcepath=/fib.py;linenumber=6;columnnumber=5;code=F841;]Local variable `x` is assigned to but never used
|
||||
##vso[task.logissue type=error;sourcepath=/undef.py;linenumber=1;columnnumber=4;code=F821;]Undefined name `a`
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/azure.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;code=invalid-syntax;]Expected one or more symbol names after import
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;code=invalid-syntax;]Expected ')', found newline
|
||||
##vso[task.logissue type=error;sourcepath=/syntax_errors.py;linenumber=1;columnnumber=15;code=invalid-syntax;]Expected one or more symbol names after import
|
||||
##vso[task.logissue type=error;sourcepath=/syntax_errors.py;linenumber=3;columnnumber=12;code=invalid-syntax;]Expected ')', found newline
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (F401),file=fib.py,line=1,col=8,endLine=1,endColumn=10::fib.py:1:8: F401 `os` imported but unused
|
||||
::error title=ty (F841),file=fib.py,line=6,col=5,endLine=6,endColumn=6::fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
||||
::error title=ty (F821),file=undef.py,line=1,col=4,endLine=1,endColumn=5::undef.py:1:4: F821 Undefined name `a`
|
||||
::error title=ty (F401),file=/fib.py,line=1,col=8,endLine=1,endColumn=10::fib.py:1:8: F401 `os` imported but unused
|
||||
::error title=ty (F841),file=/fib.py,line=6,col=5,endLine=6,endColumn=6::fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
||||
::error title=ty (F821),file=/undef.py,line=1,col=4,endLine=1,endColumn=5::undef.py:1:4: F821 Undefined name `a`
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (invalid-syntax),file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -10,7 +10,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 10,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"filename": "/notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
@@ -43,7 +43,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 12,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"filename": "/notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
@@ -76,7 +76,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 6,
|
||||
"row": 4
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"filename": "/notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
|
||||
@@ -10,7 +10,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 10,
|
||||
"row": 1
|
||||
},
|
||||
"filename": "fib.py",
|
||||
"filename": "/fib.py",
|
||||
"fix": {
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
@@ -43,7 +43,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 6,
|
||||
"row": 6
|
||||
},
|
||||
"filename": "fib.py",
|
||||
"filename": "/fib.py",
|
||||
"fix": {
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
@@ -76,7 +76,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 5,
|
||||
"row": 1
|
||||
},
|
||||
"filename": "undef.py",
|
||||
"filename": "/undef.py",
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 4,
|
||||
|
||||
@@ -10,7 +10,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 1,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "syntax_errors.py",
|
||||
"filename": "/syntax_errors.py",
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 15,
|
||||
@@ -27,7 +27,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 1,
|
||||
"row": 4
|
||||
},
|
||||
"filename": "syntax_errors.py",
|
||||
"filename": "/syntax_errors.py",
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 12,
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/json_lines.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"location":{"column":1,"row":2}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":2},"message":"`os` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":5},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"location":{"column":1,"row":2}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":2},"message":"`os` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"/notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":5},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/json_lines.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":null,"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"/fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":null,"code":"F841","end_location":{"column":6,"row":6},"filename":"/fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"/undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/json_lines.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"Expected one or more symbol names after import","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"Expected ')', found newline","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":2},"filename":"/syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"Expected one or more symbol names after import","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":4},"filename":"/syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"Expected ')', found newline","noqa_row":null,"url":null}
|
||||
|
||||
@@ -4,16 +4,16 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="ruff" tests="3" failures="3" errors="0">
|
||||
<testsuite name="fib.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff.F401" classname="fib" line="1" column="8">
|
||||
<testsuite name="/fib.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff.F401" classname="/fib" line="1" column="8">
|
||||
<failure message="`os` imported but unused">line 1, col 8, `os` imported but unused</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff.F841" classname="fib" line="6" column="5">
|
||||
<testcase name="org.ruff.F841" classname="/fib" line="6" column="5">
|
||||
<failure message="Local variable `x` is assigned to but never used">line 6, col 5, Local variable `x` is assigned to but never used</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="undef.py" tests="1" disabled="0" errors="0" failures="1" package="org.ruff">
|
||||
<testcase name="org.ruff.F821" classname="undef" line="1" column="4">
|
||||
<testsuite name="/undef.py" tests="1" disabled="0" errors="0" failures="1" package="org.ruff">
|
||||
<testcase name="org.ruff.F821" classname="/undef" line="1" column="4">
|
||||
<failure message="Undefined name `a`">line 1, col 4, Undefined name `a`</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
|
||||
@@ -4,11 +4,11 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="ruff" tests="2" failures="2" errors="0">
|
||||
<testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="1" column="15">
|
||||
<testsuite name="/syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="/syntax_errors" line="1" column="15">
|
||||
<failure message="Expected one or more symbol names after import">line 1, col 15, Expected one or more symbol names after import</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="3" column="12">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="/syntax_errors" line="3" column="12">
|
||||
<failure message="Expected ')', found newline">line 3, col 12, Expected ')', found newline</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
|
||||
@@ -10,7 +10,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"value": "F401"
|
||||
},
|
||||
"location": {
|
||||
"path": "fib.py",
|
||||
"path": "/fib.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 10,
|
||||
@@ -45,7 +45,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"value": "F841"
|
||||
},
|
||||
"location": {
|
||||
"path": "fib.py",
|
||||
"path": "/fib.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 6,
|
||||
@@ -80,7 +80,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"value": "F821"
|
||||
},
|
||||
"location": {
|
||||
"path": "undef.py",
|
||||
"path": "/undef.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 5,
|
||||
|
||||
@@ -9,7 +9,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"value": "invalid-syntax"
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
"path": "/syntax_errors.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 1,
|
||||
@@ -28,7 +28,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"value": "invalid-syntax"
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
"path": "/syntax_errors.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 1,
|
||||
|
||||
@@ -504,8 +504,8 @@ impl ToOwned for SystemPath {
|
||||
pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for SystemPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
|
||||
(self.0.capacity(), tracker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ impl ToOwned for VendoredPath {
|
||||
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for VendoredPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
|
||||
(self.0.capacity(), tracker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
104
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC240.py
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
## Valid cases:
|
||||
|
||||
def os_path_in_foo():
|
||||
file = "file.txt"
|
||||
|
||||
os.path.abspath(file) # OK
|
||||
os.path.exists(file) # OK
|
||||
os.path.split() # OK
|
||||
|
||||
async def non_io_os_path_methods():
|
||||
os.path.split() # OK
|
||||
os.path.dirname() # OK
|
||||
os.path.basename() # OK
|
||||
os.path.join() # OK
|
||||
|
||||
def pathlib_path_in_foo():
|
||||
path = Path("src/my_text.txt") # OK
|
||||
path.exists() # OK
|
||||
with path.open() as f: # OK
|
||||
...
|
||||
path = Path("src/my_text.txt").open() # OK
|
||||
|
||||
async def non_io_pathlib_path_methods():
|
||||
path = Path("src/my_text.txt")
|
||||
path.is_absolute() # OK
|
||||
path.is_relative_to() # OK
|
||||
path.as_posix() # OK
|
||||
path.relative_to() # OK
|
||||
|
||||
def inline_path_method_call():
|
||||
Path("src/my_text.txt").open() # OK
|
||||
Path("src/my_text.txt").open().flush() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def trio_path_in_foo():
|
||||
from trio import Path
|
||||
|
||||
path = Path("src/my_text.txt") # OK
|
||||
await path.absolute() # OK
|
||||
await path.exists() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def anyio_path_in_foo():
|
||||
from anyio import Path
|
||||
|
||||
path = Path("src/my_text.txt") # OK
|
||||
await path.absolute() # OK
|
||||
await path.exists() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def path_open_in_foo():
|
||||
path = Path("src/my_text.txt") # OK
|
||||
path.open() # OK, covered by ASYNC230
|
||||
|
||||
## Invalid cases:
|
||||
|
||||
async def os_path_in_foo():
|
||||
file = "file.txt"
|
||||
|
||||
os.path.abspath(file) # ASYNC240
|
||||
os.path.exists(file) # ASYNC240
|
||||
|
||||
async def pathlib_path_in_foo():
|
||||
path = Path("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def pathlib_path_in_foo():
|
||||
import pathlib
|
||||
|
||||
path = pathlib.Path("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def inline_path_method_call():
|
||||
Path("src/my_text.txt").exists() # ASYNC240
|
||||
Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
|
||||
async def aliased_path_in_foo():
|
||||
from pathlib import Path as PathAlias
|
||||
|
||||
path = PathAlias("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
global_path = Path("src/my_text.txt")
|
||||
|
||||
async def global_path_in_foo():
|
||||
global_path.exists() # ASYNC240
|
||||
|
||||
async def path_as_simple_parameter_type(path: Path):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def path_as_union_parameter_type(path: Path | None):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
|
||||
@@ -52,3 +52,21 @@ class A:
|
||||
|
||||
assert hasattr(A(), "__call__")
|
||||
assert callable(A()) is False
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20440
|
||||
def test_invalid_hasattr_calls():
|
||||
hasattr(0, "__call__", 0) # 3 args - invalid
|
||||
hasattr(0, "__call__", x=0) # keyword arg - invalid
|
||||
hasattr(0, "__call__", 0, x=0) # 3 args + keyword - invalid
|
||||
hasattr() # no args - invalid
|
||||
hasattr(0) # 1 arg - invalid
|
||||
hasattr(*(), "__call__", "extra") # unpacking - invalid
|
||||
hasattr(*()) # unpacking - invalid
|
||||
|
||||
def test_invalid_getattr_calls():
|
||||
getattr(0, "__call__", None, "extra") # 4 args - invalid
|
||||
getattr(0, "__call__", default=None) # keyword arg - invalid
|
||||
getattr() # no args - invalid
|
||||
getattr(0) # 1 arg - invalid
|
||||
getattr(*(), "__call__", None, "extra") # unpacking - invalid
|
||||
getattr(*()) # unpacking - invalid
|
||||
|
||||
33
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
from itertools import count, cycle, repeat
|
||||
|
||||
# Errors
|
||||
map(lambda x: x, [1, 2, 3])
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
||||
# Errors (limited iterators).
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
|
||||
import builtins
|
||||
# Still an error even though it uses the qualified name
|
||||
builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
|
||||
# OK
|
||||
map(lambda x: x, [1, 2, 3], strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True), strict=True)
|
||||
|
||||
# OK (single iterable - no strict required)
|
||||
map(lambda x: x, [1, 2, 3])
|
||||
|
||||
# OK (infinite iterators)
|
||||
map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3]))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None))
|
||||
map(lambda x, y: x + y, [1, 2, 3], count())
|
||||
@@ -19,3 +19,18 @@ class MyClass:
|
||||
|
||||
def attribute_usage(self) -> id:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
@staticmethod
|
||||
def property(f):
|
||||
return f
|
||||
|
||||
id = 1
|
||||
|
||||
@[property][0]
|
||||
def f(self, x=[id]):
|
||||
return x
|
||||
|
||||
bin = 2
|
||||
foo = [bin]
|
||||
|
||||
@@ -42,3 +42,6 @@ tuple(
|
||||
x for x in [1,2,3]
|
||||
}
|
||||
)
|
||||
|
||||
t9 = tuple([1],)
|
||||
t10 = tuple([1, 2],)
|
||||
|
||||
18
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI021_1.pyi
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
from contextlib import nullcontext
|
||||
|
||||
|
||||
def check_isolation_level(mode: int) -> None:
|
||||
"""Will report both, but only fix the first.""" # ERROR PYI021
|
||||
... # ERROR PIE790
|
||||
|
||||
|
||||
with nullcontext():
|
||||
"""Should not report."""
|
||||
# add something thats not a pass here
|
||||
# to not get a remove unnecessary pass err
|
||||
x = 0
|
||||
|
||||
if True:
|
||||
"""Should not report."""
|
||||
# same as above
|
||||
y = 1
|
||||
@@ -170,3 +170,4 @@ print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
# leading/trailing whitespace should not count towards maxsplit
|
||||
" a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
" a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
"a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
||||
3
crates/ruff_linter/resources/test/fixtures/isort/required_imports/plr0402_skip.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import concurrent.futures as futures
|
||||
|
||||
1
|
||||
@@ -0,0 +1,3 @@
|
||||
import concurrent.futures as futures
|
||||
|
||||
1
|
||||
@@ -287,3 +287,19 @@ class C(B):
|
||||
def f(self):
|
||||
C = B # Local variable C shadows the class name
|
||||
return super(C, self).f() # Should NOT trigger UP008
|
||||
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/20491
|
||||
# UP008 should not apply when __class__ is a local variable
|
||||
class A:
|
||||
def f(self):
|
||||
return 1
|
||||
|
||||
class B(A):
|
||||
def f(self):
|
||||
return 2
|
||||
|
||||
class C(B):
|
||||
def f(self):
|
||||
__class__ = B # Local variable __class__ shadows the implicit __class__
|
||||
return super(__class__, self).f() # Should NOT trigger UP008
|
||||
|
||||
39
crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import logging
|
||||
|
||||
# %s + str()
|
||||
logging.info("Hello %s", str("World!"))
|
||||
logging.log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
||||
# %s + repr()
|
||||
logging.info("Hello %s", repr("World!"))
|
||||
logging.log(logging.INFO, "Hello %s", repr("World!"))
|
||||
|
||||
# %r + str()
|
||||
logging.info("Hello %r", str("World!"))
|
||||
logging.log(logging.INFO, "Hello %r", str("World!"))
|
||||
|
||||
# %r + repr()
|
||||
logging.info("Hello %r", repr("World!"))
|
||||
logging.log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
||||
from logging import info, log
|
||||
|
||||
# %s + str()
|
||||
info("Hello %s", str("World!"))
|
||||
log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
||||
# %s + repr()
|
||||
info("Hello %s", repr("World!"))
|
||||
log(logging.INFO, "Hello %s", repr("World!"))
|
||||
|
||||
# %r + str()
|
||||
info("Hello %r", str("World!"))
|
||||
log(logging.INFO, "Hello %r", str("World!"))
|
||||
|
||||
# %r + repr()
|
||||
info("Hello %r", repr("World!"))
|
||||
log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
||||
def str(s): return f"str = {s}"
|
||||
# Don't flag this
|
||||
logging.info("Hello %s", str("World!"))
|
||||
@@ -145,9 +145,6 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
// flake8-pyi
|
||||
if enforce_stubs {
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, definition, docstring);
|
||||
}
|
||||
if enforce_stubs_and_runtime {
|
||||
flake8_pyi::rules::iter_method_return_iterable(checker, definition);
|
||||
}
|
||||
|
||||
@@ -212,13 +212,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if ctx.is_store() {
|
||||
let check_too_many_expressions =
|
||||
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
||||
let check_two_starred_expressions =
|
||||
checker.is_rule_enabled(Rule::MultipleStarredExpressions);
|
||||
pyflakes::rules::starred_expressions(
|
||||
checker,
|
||||
elts,
|
||||
check_too_many_expressions,
|
||||
check_two_starred_expressions,
|
||||
expr.range(),
|
||||
);
|
||||
}
|
||||
@@ -669,6 +666,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::BlockingOpenCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_open_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingPathMethodInAsyncFunction) {
|
||||
flake8_async::rules::blocking_os_path(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::CreateSubprocessInAsyncFunction,
|
||||
Rule::RunProcessInAsyncFunction,
|
||||
@@ -717,7 +717,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_bugbear::rules::re_sub_positional_args(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UnreliableCallableCheck) {
|
||||
flake8_bugbear::rules::unreliable_callable_check(checker, expr, func, args);
|
||||
flake8_bugbear::rules::unreliable_callable_check(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StripWithMultiCharacters) {
|
||||
flake8_bugbear::rules::strip_with_multi_characters(checker, expr, func, args);
|
||||
@@ -741,6 +743,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_bugbear::rules::zip_without_explicit_strict(checker, call);
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::MapWithoutExplicitStrict) {
|
||||
if checker.target_version() >= PythonVersion::PY314 {
|
||||
flake8_bugbear::rules::map_without_explicit_strict(checker, call);
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::NoExplicitStacklevel) {
|
||||
flake8_bugbear::rules::no_explicit_stacklevel(checker, call);
|
||||
}
|
||||
@@ -1279,6 +1286,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::UnnecessaryEmptyIterableWithinDequeCall) {
|
||||
ruff::rules::unnecessary_literal_within_deque_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::LoggingEagerConversion) {
|
||||
ruff::rules::logging_eager_conversion(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StarmapZip) {
|
||||
ruff::rules::starmap_zip(checker, call);
|
||||
}
|
||||
|
||||
@@ -803,11 +803,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
for alias in names {
|
||||
if let Some("__future__") = module {
|
||||
if checker.is_rule_enabled(Rule::FutureFeatureNotDefined) {
|
||||
pyflakes::rules::future_feature_not_defined(checker, alias);
|
||||
}
|
||||
} else if &alias.name == "*" {
|
||||
if module != Some("__future__") && &alias.name == "*" {
|
||||
// F403
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::UndefinedLocalWithImportStar {
|
||||
|
||||
@@ -2,14 +2,17 @@ use ruff_python_ast::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::flake8_pie;
|
||||
use crate::rules::refurb;
|
||||
use crate::rules::{flake8_pie, flake8_pyi};
|
||||
|
||||
/// Run lint rules over a suite of [`Stmt`] syntax nodes.
|
||||
pub(crate) fn suite(suite: &[Stmt], checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::UnnecessaryPlaceholder) {
|
||||
flake8_pie::rules::unnecessary_placeholder(checker, suite);
|
||||
}
|
||||
if checker.source_type.is_stub() && checker.is_rule_enabled(Rule::DocstringInStub) {
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, suite);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::RepeatedGlobal) {
|
||||
refurb::rules::repeated_global(checker, suite);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ use itertools::Itertools;
|
||||
use log::debug;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, IntoDiagnosticMessage, Span};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticTag, IntoDiagnosticMessage, Span};
|
||||
use ruff_diagnostics::{Applicability, Fix, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
|
||||
@@ -56,7 +56,7 @@ use ruff_python_semantic::{
|
||||
Import, Module, ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel,
|
||||
SemanticModelFlags, StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{MAGIC_GLOBALS, python_builtins};
|
||||
use ruff_python_stdlib::builtins::{python_builtins, python_magic_globals};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{OneIndexed, SourceFile, SourceFileBuilder, SourceRow};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
@@ -69,8 +69,8 @@ use crate::package::PackageRoot;
|
||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pyflakes::rules::{
|
||||
LateFutureImport, ReturnOutsideFunction, UndefinedLocalWithNestedImportStarUsage,
|
||||
YieldOutsideFunction,
|
||||
LateFutureImport, MultipleStarredExpressions, ReturnOutsideFunction,
|
||||
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
||||
};
|
||||
use crate::rules::pylint::rules::{
|
||||
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, YieldFromInAsyncFunction,
|
||||
@@ -87,7 +87,7 @@ mod deferred;
|
||||
|
||||
/// State representing whether a docstring is expected or not for the next statement.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum DocstringState {
|
||||
pub(crate) enum DocstringState {
|
||||
/// The next statement is expected to be a docstring, but not necessarily so.
|
||||
///
|
||||
/// For example, in the following code:
|
||||
@@ -128,7 +128,7 @@ impl DocstringState {
|
||||
|
||||
/// The kind of an expected docstring.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum ExpectedDocstringKind {
|
||||
pub(crate) enum ExpectedDocstringKind {
|
||||
/// A module-level docstring.
|
||||
///
|
||||
/// For example,
|
||||
@@ -603,6 +603,11 @@ impl<'a> Checker<'a> {
|
||||
pub(crate) const fn context(&self) -> &'a LintContext<'a> {
|
||||
self.context
|
||||
}
|
||||
|
||||
/// Return the current [`DocstringState`].
|
||||
pub(crate) fn docstring_state(&self) -> DocstringState {
|
||||
self.docstring_state
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TypingImporter<'a, 'b> {
|
||||
@@ -685,6 +690,20 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.report_diagnostic(YieldFromInAsyncFunction, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::MultipleStarredExpressions => {
|
||||
// F622
|
||||
if self.is_rule_enabled(Rule::MultipleStarredExpressions) {
|
||||
self.report_diagnostic(MultipleStarredExpressions, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::FutureFeatureNotDefined(name) => {
|
||||
if self.is_rule_enabled(Rule::FutureFeatureNotDefined) {
|
||||
self.report_diagnostic(
|
||||
pyflakes::rules::FutureFeatureNotDefined { name },
|
||||
error.range,
|
||||
);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||
@@ -2550,7 +2569,7 @@ impl<'a> Checker<'a> {
|
||||
for builtin in standard_builtins {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in MAGIC_GLOBALS {
|
||||
for builtin in python_magic_globals(target_version.minor) {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in &settings.builtins {
|
||||
@@ -3307,6 +3326,56 @@ impl DiagnosticGuard<'_, '_> {
|
||||
pub(crate) fn defuse(mut self) {
|
||||
self.diagnostic = None;
|
||||
}
|
||||
|
||||
/// Set the message on the primary annotation for this diagnostic.
|
||||
///
|
||||
/// If a message already exists on the primary annotation, then this
|
||||
/// overwrites the existing message.
|
||||
///
|
||||
/// This message is associated with the primary annotation created
|
||||
/// for every `Diagnostic` that uses the `DiagnosticGuard` API.
|
||||
/// Specifically, the annotation is derived from the `TextRange` given to
|
||||
/// the `LintContext::report_diagnostic` API.
|
||||
///
|
||||
/// Callers can add additional primary or secondary annotations via the
|
||||
/// `DerefMut` trait implementation to a `Diagnostic`.
|
||||
pub(crate) fn set_primary_message(&mut self, message: impl IntoDiagnosticMessage) {
|
||||
// N.B. It is normally bad juju to define `self` methods
|
||||
// on types that implement `Deref`. Instead, it's idiomatic
|
||||
// to do `fn foo(this: &mut LintDiagnosticGuard)`, which in
|
||||
// turn forces callers to use
|
||||
// `LintDiagnosticGuard(&mut guard, message)`. But this is
|
||||
// supremely annoying for what is expected to be a common
|
||||
// case.
|
||||
//
|
||||
// Moreover, most of the downside that comes from these sorts
|
||||
// of methods is a semver hazard. Because the deref target type
|
||||
// could also define a method by the same name, and that leads
|
||||
// to confusion. But we own all the code involved here and
|
||||
// there is no semver boundary. So... ¯\_(ツ)_/¯ ---AG
|
||||
|
||||
// OK because we know the diagnostic was constructed with a single
|
||||
// primary annotation that will always come before any other annotation
|
||||
// in the diagnostic. (This relies on the `Diagnostic` API not exposing
|
||||
// any methods for removing annotations or re-ordering them, which is
|
||||
// true as of 2025-04-11.)
|
||||
let ann = self.primary_annotation_mut().unwrap();
|
||||
ann.set_message(message);
|
||||
}
|
||||
|
||||
/// Adds a tag on the primary annotation for this diagnostic.
|
||||
///
|
||||
/// This tag is associated with the primary annotation created
|
||||
/// for every `Diagnostic` that uses the `DiagnosticGuard` API.
|
||||
/// Specifically, the annotation is derived from the `TextRange` given to
|
||||
/// the `LintContext::report_diagnostic` API.
|
||||
///
|
||||
/// Callers can add additional primary or secondary annotations via the
|
||||
/// `DerefMut` trait implementation to a `Diagnostic`.
|
||||
pub(crate) fn add_primary_tag(&mut self, tag: DiagnosticTag) {
|
||||
let ann = self.primary_annotation_mut().unwrap();
|
||||
ann.push_tag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagnosticGuard<'_, '_> {
|
||||
|
||||
@@ -341,6 +341,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
|
||||
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
|
||||
(Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction),
|
||||
(Flake8Async, "240") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingPathMethodInAsyncFunction),
|
||||
(Flake8Async, "250") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingInputInAsyncFunction),
|
||||
(Flake8Async, "251") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingSleepInAsyncFunction),
|
||||
|
||||
@@ -394,6 +395,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
|
||||
(Flake8Bugbear, "911") => (RuleGroup::Stable, rules::flake8_bugbear::rules::BatchedWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "912") => (RuleGroup::Preview, rules::flake8_bugbear::rules::MapWithoutExplicitStrict),
|
||||
|
||||
// flake8-blind-except
|
||||
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
||||
@@ -1051,6 +1053,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
|
||||
(Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
|
||||
(Ruff, "065") => (RuleGroup::Preview, rules::ruff::rules::LoggingEagerConversion),
|
||||
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn remove_imports<'a>(
|
||||
if member == "*" {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", member);
|
||||
bail!("Expected \"*\" for unused import (got: \"{member}\")");
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
|
||||
@@ -228,3 +228,10 @@ pub(crate) const fn is_sim910_expanded_key_support_enabled(settings: &LinterSett
|
||||
pub(crate) const fn is_fix_builtin_open_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20178
|
||||
pub(crate) const fn is_a003_class_scope_shadowing_expansion_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ mod tests {
|
||||
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::BlockingOpenCallInAsyncFunction, Path::new("ASYNC230.py"))]
|
||||
#[test_case(Rule::BlockingPathMethodInAsyncFunction, Path::new("ASYNC240.py"))]
|
||||
#[test_case(Rule::BlockingInputInAsyncFunction, Path::new("ASYNC250.py"))]
|
||||
#[test_case(Rule::BlockingSleepInAsyncFunction, Path::new("ASYNC251.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_union_and_optional};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not call blocking `os.path` or `pathlib.Path`
|
||||
/// methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling some `os.path` or `pathlib.Path` methods in an async function will block
|
||||
/// the entire event loop, preventing it from executing other tasks while waiting
|
||||
/// for the operation. This negates the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead, use the methods' async equivalents from `trio.Path` or `anyio.Path`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = "my_file.txt"
|
||||
/// file_exists = os.path.exists(path)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import trio
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = trio.Path("my_file.txt")
|
||||
/// file_exists = await path.exists()
|
||||
/// ```
|
||||
///
|
||||
/// Non-blocking methods are OK to use:
|
||||
/// ```python
|
||||
/// import pathlib
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = pathlib.Path("my_file.txt")
|
||||
/// file_dirname = path.dirname()
|
||||
/// new_path = os.path.join("/tmp/src/", path)
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingPathMethodInAsyncFunction {
|
||||
path_library: String,
|
||||
}
|
||||
|
||||
impl Violation for BlockingPathMethodInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Async functions should not use {path_library} methods, use trio.Path or anyio.path",
|
||||
path_library = self.path_library
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC240
|
||||
pub(crate) fn blocking_os_path(checker: &Checker, call: &ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
if !semantic.in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an expression is calling I/O related os.path method.
|
||||
// Just initializing pathlib.Path object is OK, we can return
|
||||
// early in that scenario.
|
||||
if let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) {
|
||||
let segments = qualified_name.segments();
|
||||
if !matches!(segments, ["os", "path", _]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(os_path_method) = segments.last() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if maybe_calling_io_operation(os_path_method) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "os.path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = call.func.as_attribute_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !maybe_calling_io_operation(attr.id.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an expression is a pathlib.Path constructor that directly
|
||||
// calls an I/O method.
|
||||
if PathlibPathChecker::match_initializer(value, semantic) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "pathlib.Path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lastly, check if a variable is a pathlib.Path instance and it's
|
||||
// calling an I/O method.
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if check_type::<PathlibPathChecker>(binding, semantic) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "pathlib.Path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
struct PathlibPathChecker;
|
||||
|
||||
impl PathlibPathChecker {
|
||||
fn is_pathlib_path_constructor(
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
expr: &Expr,
|
||||
) -> bool {
|
||||
let Some(qualified_name) = semantic.resolve_qualified_name(expr) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"pathlib",
|
||||
"Path"
|
||||
| "PosixPath"
|
||||
| "PurePath"
|
||||
| "PurePosixPath"
|
||||
| "PureWindowsPath"
|
||||
| "WindowsPath"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeChecker for PathlibPathChecker {
|
||||
fn match_annotation(annotation: &Expr, semantic: &ruff_python_semantic::SemanticModel) -> bool {
|
||||
if Self::is_pathlib_path_constructor(semantic, annotation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut found = false;
|
||||
traverse_union_and_optional(
|
||||
&mut |inner_expr, _| {
|
||||
if Self::is_pathlib_path_constructor(semantic, inner_expr) {
|
||||
found = true;
|
||||
}
|
||||
},
|
||||
semantic,
|
||||
annotation,
|
||||
);
|
||||
found
|
||||
}
|
||||
|
||||
fn match_initializer(
|
||||
initializer: &Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = initializer else {
|
||||
return false;
|
||||
};
|
||||
|
||||
Self::is_pathlib_path_constructor(semantic, func)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_calling_io_operation(attr: &str) -> bool {
|
||||
// ".open()" is added to the allow list to let ASYNC 230 handle
|
||||
// that case.
|
||||
!matches!(
|
||||
attr,
|
||||
"ALLOW_MISSING"
|
||||
| "altsep"
|
||||
| "anchor"
|
||||
| "as_posix"
|
||||
| "as_uri"
|
||||
| "basename"
|
||||
| "commonpath"
|
||||
| "commonprefix"
|
||||
| "curdir"
|
||||
| "defpath"
|
||||
| "devnull"
|
||||
| "dirname"
|
||||
| "drive"
|
||||
| "expandvars"
|
||||
| "extsep"
|
||||
| "genericpath"
|
||||
| "is_absolute"
|
||||
| "is_relative_to"
|
||||
| "is_reserved"
|
||||
| "isabs"
|
||||
| "join"
|
||||
| "joinpath"
|
||||
| "match"
|
||||
| "name"
|
||||
| "normcase"
|
||||
| "os"
|
||||
| "open"
|
||||
| "pardir"
|
||||
| "parent"
|
||||
| "parents"
|
||||
| "parts"
|
||||
| "pathsep"
|
||||
| "relative_to"
|
||||
| "root"
|
||||
| "samestat"
|
||||
| "sep"
|
||||
| "split"
|
||||
| "splitdrive"
|
||||
| "splitext"
|
||||
| "splitroot"
|
||||
| "stem"
|
||||
| "suffix"
|
||||
| "suffixes"
|
||||
| "supports_unicode_filenames"
|
||||
| "sys"
|
||||
| "with_name"
|
||||
| "with_segments"
|
||||
| "with_stem"
|
||||
| "with_suffix"
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub(crate) use blocking_http_call::*;
|
||||
pub(crate) use blocking_http_call_httpx::*;
|
||||
pub(crate) use blocking_input::*;
|
||||
pub(crate) use blocking_open_call::*;
|
||||
pub(crate) use blocking_path_methods::*;
|
||||
pub(crate) use blocking_process_invocation::*;
|
||||
pub(crate) use blocking_sleep::*;
|
||||
pub(crate) use cancel_scope_no_checkpoint::*;
|
||||
@@ -18,6 +19,7 @@ mod blocking_http_call;
|
||||
mod blocking_http_call_httpx;
|
||||
mod blocking_input;
|
||||
mod blocking_open_call;
|
||||
mod blocking_path_methods;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
mod cancel_scope_no_checkpoint;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:67:5
|
||||
|
|
||||
65 | file = "file.txt"
|
||||
66 |
|
||||
67 | os.path.abspath(file) # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^
|
||||
68 | os.path.exists(file) # ASYNC240
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:68:5
|
||||
|
|
||||
67 | os.path.abspath(file) # ASYNC240
|
||||
68 | os.path.exists(file) # ASYNC240
|
||||
| ^^^^^^^^^^^^^^
|
||||
69 |
|
||||
70 | async def pathlib_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:72:5
|
||||
|
|
||||
70 | async def pathlib_path_in_foo():
|
||||
71 | path = Path("src/my_text.txt")
|
||||
72 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
73 |
|
||||
74 | async def pathlib_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:78:5
|
||||
|
|
||||
77 | path = pathlib.Path("src/my_text.txt")
|
||||
78 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
79 |
|
||||
80 | async def inline_path_method_call():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:81:5
|
||||
|
|
||||
80 | async def inline_path_method_call():
|
||||
81 | Path("src/my_text.txt").exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 | Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:82:5
|
||||
|
|
||||
80 | async def inline_path_method_call():
|
||||
81 | Path("src/my_text.txt").exists() # ASYNC240
|
||||
82 | Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
83 |
|
||||
84 | async def aliased_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:88:5
|
||||
|
|
||||
87 | path = PathAlias("src/my_text.txt")
|
||||
88 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
89 |
|
||||
90 | global_path = Path("src/my_text.txt")
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:93:5
|
||||
|
|
||||
92 | async def global_path_in_foo():
|
||||
93 | global_path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
94 |
|
||||
95 | async def path_as_simple_parameter_type(path: Path):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:96:5
|
||||
|
|
||||
95 | async def path_as_simple_parameter_type(path: Path):
|
||||
96 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
97 |
|
||||
98 | async def path_as_union_parameter_type(path: Path | None):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:99:5
|
||||
|
|
||||
98 | async def path_as_union_parameter_type(path: Path | None):
|
||||
99 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
100 |
|
||||
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:102:5
|
||||
|
|
||||
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
102 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
@@ -23,6 +23,17 @@ use crate::rules::flake8_bandit::helpers::string_literal;
|
||||
/// Avoid using weak or broken cryptographic hash functions in security
|
||||
/// contexts. Instead, use a known secure hash function such as SHA256.
|
||||
///
|
||||
/// Note: This rule targets the following weak algorithm names in `hashlib`:
|
||||
/// `md4`, `md5`, `sha`, and `sha1`. It also flags uses of `crypt.crypt` and
|
||||
/// `crypt.mksalt` when configured with `METHOD_CRYPT`, `METHOD_MD5`, or
|
||||
/// `METHOD_BLOWFISH`.
|
||||
///
|
||||
/// It does not attempt to lint OpenSSL- or platform-specific aliases and OIDs
|
||||
/// (for example: `"sha-1"`, `"ssl3-sha1"`, `"ssl3-md5"`, or
|
||||
/// `"1.3.14.3.2.26"`), nor variations with trailing spaces, as the set of
|
||||
/// accepted aliases depends on the underlying OpenSSL version and varies across
|
||||
/// platforms and Python builds.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import hashlib
|
||||
|
||||
@@ -4,6 +4,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
|
||||
/// Return `true` if the statement containing the current expression is the last
|
||||
/// top-level expression in the cell. This assumes that the source is a Jupyter
|
||||
@@ -27,3 +28,54 @@ pub(super) fn at_last_top_level_expression_in_cell(
|
||||
.all(|token| token.kind() == SimpleTokenKind::Semi || token.kind().is_trivia())
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||
/// `itertools.cycle` or similar).
|
||||
pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| match qualified_name.segments() {
|
||||
["itertools", "cycle" | "count"] => true,
|
||||
["itertools", "repeat"] => {
|
||||
// Ex) `itertools.repeat(1)`
|
||||
if keywords.is_empty() && args.len() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times")
|
||||
&& keyword.value.is_none_literal_expr()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if any expression in the iterator appears to be an infinite iterator.
|
||||
pub(crate) fn any_infinite_iterables<'a>(
|
||||
iter: impl IntoIterator<Item = &'a Expr>,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
iter.into_iter()
|
||||
.any(|arg| is_infinite_iterable(arg, semantic))
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ mod tests {
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
#[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))]
|
||||
@@ -81,11 +82,35 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
unresolved_target_version: PythonVersion::PY314.into(),
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::ClassAsDataStructure,
|
||||
Path::new("class_as_data_structure.py"),
|
||||
PythonVersion::PY39
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::MapWithoutExplicitStrict,
|
||||
Path::new("B912.py"),
|
||||
PythonVersion::PY313
|
||||
)]
|
||||
fn rules_with_target_version(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::ExprCall;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_bugbear::rules::is_infinite_iterable;
|
||||
use crate::rules::flake8_bugbear::helpers::is_infinite_iterable;
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::add_argument;
|
||||
use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables.
|
||||
///
|
||||
/// This rule applies to Python 3.14 and later, where `map` accepts a `strict` keyword
|
||||
/// argument. For details, see: [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By default, if the iterables passed to `map` are of different lengths, the
|
||||
/// resulting iterator will be silently truncated to the length of the shortest
|
||||
/// iterable. This can lead to subtle bugs.
|
||||
///
|
||||
/// Pass `strict=True` to raise a `ValueError` if the iterables are of
|
||||
/// non-uniform length. Alternatively, if the iterables are deliberately of
|
||||
/// different lengths, pass `strict=False` to make the intention explicit.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// map(f, a, b)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// map(f, a, b, strict=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe for `map` calls that contain
|
||||
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead
|
||||
/// to a duplicate keyword argument error.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map)
|
||||
/// - [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MapWithoutExplicitStrict;
|
||||
|
||||
impl AlwaysFixableViolation for MapWithoutExplicitStrict {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`map()` without an explicit `strict=` parameter".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add explicit value for parameter `strict=`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// B912
|
||||
pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "map")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& call.arguments.args.len() >= 3 // function + at least 2 iterables
|
||||
&& !any_infinite_iterables(call.arguments.args.iter().skip(1), semantic)
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
// If the function call contains `**kwargs`, mark the fix as unsafe.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub(crate) use getattr_with_constant::*;
|
||||
pub(crate) use jump_statement_in_finally::*;
|
||||
pub(crate) use loop_iterator_mutation::*;
|
||||
pub(crate) use loop_variable_overrides_iterator::*;
|
||||
pub(crate) use map_without_explicit_strict::*;
|
||||
pub(crate) use mutable_argument_default::*;
|
||||
pub(crate) use mutable_contextvar_default::*;
|
||||
pub(crate) use no_explicit_stacklevel::*;
|
||||
@@ -56,6 +57,7 @@ mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_iterator_mutation;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod map_without_explicit_strict;
|
||||
mod mutable_argument_default;
|
||||
mod mutable_contextvar_default;
|
||||
mod no_explicit_stacklevel;
|
||||
|
||||
@@ -90,7 +90,11 @@ pub(crate) fn unreliable_callable_check(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[ast::Keyword],
|
||||
) {
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
@@ -103,7 +107,21 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Some(builtins_function) = checker.semantic().resolve_builtin_symbol(func) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(builtins_function, "hasattr" | "getattr") {
|
||||
|
||||
// Validate function arguments based on function name
|
||||
let valid_args = match builtins_function {
|
||||
"hasattr" => {
|
||||
// hasattr should have exactly 2 positional arguments and no keywords
|
||||
args.len() == 2
|
||||
}
|
||||
"getattr" => {
|
||||
// getattr should have 2 or 3 positional arguments and no keywords
|
||||
args.len() == 2 || args.len() == 3
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !valid_args {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::add_argument;
|
||||
use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
@@ -57,11 +57,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "zip")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& !call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(|arg| is_infinite_iterable(arg, semantic))
|
||||
&& !any_infinite_iterables(call.arguments.args.iter(), semantic)
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(ZipWithoutExplicitStrict, call.range())
|
||||
@@ -86,47 +82,3 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||
/// `itertools.cycle` or similar).
|
||||
pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
match qualified_name.segments() {
|
||||
["itertools", "cycle" | "count"] => true,
|
||||
["itertools", "repeat"] => {
|
||||
// Ex) `itertools.repeat(1)`
|
||||
if keywords.is_empty() && args.len() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `iterools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,4 +156,6 @@ help: Replace with `callable()`
|
||||
- assert hasattr(A(), "__call__")
|
||||
53 + assert callable(A())
|
||||
54 | assert callable(A()) is False
|
||||
55 |
|
||||
56 | # https://github.com/astral-sh/ruff/issues/20440
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:5:1
|
||||
|
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
2 |
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
5 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:6:1
|
||||
|
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
- map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
6 + map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9], strict=False)
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:7:1
|
||||
|
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
8 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:9:1
|
||||
|
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
9 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True), strict=False)
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:12:1
|
||||
|
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
- map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
12 + map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1), strict=False)
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
14 |
|
||||
15 | import builtins
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:13:1
|
||||
|
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
14 |
|
||||
15 | import builtins
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
- map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
13 + map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4), strict=False)
|
||||
14 |
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:17:1
|
||||
|
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
17 | builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
18 |
|
||||
19 | # OK
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
14 |
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
- builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
17 + builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
18 |
|
||||
19 | # OK
|
||||
20 | map(lambda x: x, [1, 2, 3], strict=True)
|
||||
@@ -14,6 +14,7 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_builtins;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::{test_path, test_resource_path};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
@@ -63,6 +64,28 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_builtins").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
flake8_builtins: flake8_builtins::settings::Settings {
|
||||
strict_checking: true,
|
||||
..Default::default()
|
||||
},
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::StdlibModuleShadowing,
|
||||
Path::new("A005/modules/utils/logging.py"),
|
||||
|
||||
@@ -6,6 +6,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_a003_class_scope_shadowing_expansion_enabled;
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
@@ -123,16 +124,26 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
// def repeat(value: int, times: int) -> list[int]:
|
||||
// return [value] * times
|
||||
// ```
|
||||
// In stable, only consider references whose first non-type parent scope is the class
|
||||
// scope (e.g., decorators, default args, and attribute initializers).
|
||||
// In preview, also consider references from within the class scope.
|
||||
let consider_reference = |reference_scope_id: ScopeId| {
|
||||
if is_a003_class_scope_shadowing_expansion_enabled(checker.settings()) {
|
||||
if reference_scope_id == scope_id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
checker
|
||||
.semantic()
|
||||
.first_non_type_parent_scope_id(reference_scope_id)
|
||||
== Some(scope_id)
|
||||
};
|
||||
|
||||
for reference in binding
|
||||
.references
|
||||
.iter()
|
||||
.map(|reference_id| checker.semantic().reference(*reference_id))
|
||||
.filter(|reference| {
|
||||
checker
|
||||
.semantic()
|
||||
.first_non_type_parent_scope_id(reference.scope_id())
|
||||
== Some(scope_id)
|
||||
})
|
||||
.filter(|reference| consider_reference(reference.scope_id()))
|
||||
{
|
||||
checker.report_diagnostic(
|
||||
BuiltinAttributeShadowing {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A003 Python builtin is shadowed by method `str` from line 14
|
||||
--> A003.py:17:31
|
||||
|
|
||||
15 | pass
|
||||
16 |
|
||||
17 | def method_usage(self) -> str:
|
||||
| ^^^
|
||||
18 | pass
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `id` from line 3
|
||||
--> A003.py:20:34
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | def attribute_usage(self) -> id:
|
||||
| ^^
|
||||
21 | pass
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by method `property` from line 26
|
||||
--> A003.py:31:7
|
||||
|
|
||||
29 | id = 1
|
||||
30 |
|
||||
31 | @[property][0]
|
||||
| ^^^^^^^^
|
||||
32 | def f(self, x=[id]):
|
||||
33 | return x
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `id` from line 29
|
||||
--> A003.py:32:20
|
||||
|
|
||||
31 | @[property][0]
|
||||
32 | def f(self, x=[id]):
|
||||
| ^^
|
||||
33 | return x
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `bin` from line 35
|
||||
--> A003.py:36:12
|
||||
|
|
||||
35 | bin = 2
|
||||
36 | foo = [bin]
|
||||
| ^^^
|
||||
|
|
||||
@@ -124,7 +124,7 @@ pub(crate) fn unnecessary_literal_within_tuple_call(
|
||||
let needs_trailing_comma = if let [item] = elts.as_slice() {
|
||||
SimpleTokenizer::new(
|
||||
checker.locator().contents(),
|
||||
TextRange::new(item.end(), call.end()),
|
||||
TextRange::new(item.end(), argument.end()),
|
||||
)
|
||||
.all(|token| token.kind != SimpleTokenKind::Comma)
|
||||
} else {
|
||||
|
||||
@@ -247,3 +247,36 @@ help: Rewrite as a tuple literal
|
||||
28 | tuple([x for x in range(5)])
|
||||
29 | tuple({x for x in range(10)})
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:46:6
|
||||
|
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
| ^^^^^^^^^^^
|
||||
47 | t10 = tuple([1, 2],)
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
43 | }
|
||||
44 | )
|
||||
45 |
|
||||
- t9 = tuple([1],)
|
||||
46 + t9 = (1,)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:47:7
|
||||
|
|
||||
46 | t9 = tuple([1],)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
- t10 = tuple([1, 2],)
|
||||
47 + t10 = (1, 2)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -344,3 +344,36 @@ help: Rewrite as a generator
|
||||
42 | x for x in [1,2,3]
|
||||
43 | }
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:46:6
|
||||
|
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
| ^^^^^^^^^^^
|
||||
47 | t10 = tuple([1, 2],)
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
43 | }
|
||||
44 | )
|
||||
45 |
|
||||
- t9 = tuple([1],)
|
||||
46 + t9 = (1,)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:47:7
|
||||
|
|
||||
46 | t9 = tuple([1],)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
- t10 = tuple([1, 2],)
|
||||
47 + t10 = (1, 2)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use ruff_python_ast::InterpolatedStringElement;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
|
||||
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_f_string_logging_enabled;
|
||||
@@ -198,7 +199,7 @@ fn check_log_record_attr_clash(checker: &Checker, extra: &Keyword) {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum LoggingCallType {
|
||||
pub(crate) enum LoggingCallType {
|
||||
/// Logging call with a level method, e.g., `logging.info`.
|
||||
LevelCall(LoggingLevel),
|
||||
/// Logging call with an integer level as an argument, e.g., `logger.log(level, ...)`.
|
||||
@@ -215,39 +216,41 @@ impl LoggingCallType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check logging calls for violations.
|
||||
pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
pub(crate) fn find_logging_call(
|
||||
checker: &Checker,
|
||||
call: &ast::ExprCall,
|
||||
) -> Option<(LoggingCallType, TextRange)> {
|
||||
// Determine the call type (e.g., `info` vs. `exception`) and the range of the attribute.
|
||||
let (logging_call_type, range) = match call.func.as_ref() {
|
||||
match call.func.as_ref() {
|
||||
Expr::Attribute(ast::ExprAttribute { value: _, attr, .. }) => {
|
||||
let Some(call_type) = LoggingCallType::from_attribute(attr.as_str()) else {
|
||||
return;
|
||||
};
|
||||
let call_type = LoggingCallType::from_attribute(attr.as_str())?;
|
||||
if !logging::is_logger_candidate(
|
||||
&call.func,
|
||||
checker.semantic(),
|
||||
&checker.settings().logger_objects,
|
||||
) {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
(call_type, attr.range())
|
||||
Some((call_type, attr.range()))
|
||||
}
|
||||
Expr::Name(_) => {
|
||||
let Some(qualified_name) = checker
|
||||
let qualified_name = checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.resolve_qualified_name(call.func.as_ref())?;
|
||||
let ["logging", attribute] = qualified_name.segments() else {
|
||||
return;
|
||||
return None;
|
||||
};
|
||||
let Some(call_type) = LoggingCallType::from_attribute(attribute) else {
|
||||
return;
|
||||
};
|
||||
(call_type, call.func.range())
|
||||
let call_type = LoggingCallType::from_attribute(attribute)?;
|
||||
Some((call_type, call.func.range()))
|
||||
}
|
||||
_ => return,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check logging calls for violations.
|
||||
pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
let Some((logging_call_type, range)) = find_logging_call(checker, call) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// G001, G002, G003, G004
|
||||
|
||||
@@ -71,6 +71,8 @@ pub(crate) fn implicit_namespace_package(
|
||||
if package.is_none()
|
||||
// Ignore non-`.py` files, which don't require an `__init__.py`.
|
||||
&& PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file)
|
||||
// Ignore `.pyw` files that are also PySourceType::Python but aren't importable namespaces
|
||||
&& path.extension().is_some_and(|ext| ext == "py")
|
||||
// Ignore any files that are direct children of the project root.
|
||||
&& path
|
||||
.parent()
|
||||
|
||||
@@ -192,4 +192,17 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("PYI021_1.pyi"))]
|
||||
fn pyi021_pie790_isolation_check(path: &Path) -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_pyi").join(path).as_path(),
|
||||
&settings::LinterSettings::for_rules([
|
||||
Rule::DocstringInStub,
|
||||
Rule::UnnecessaryPlaceholder,
|
||||
]),
|
||||
)?;
|
||||
assert_diagnostics!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprStringLiteral;
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_python_ast::{ExprStringLiteral, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checkers::ast::{Checker, DocstringState, ExpectedDocstringKind};
|
||||
use crate::docstrings::extraction::docstring_from;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
|
||||
/// ## What it does
|
||||
@@ -41,26 +41,34 @@ impl AlwaysFixableViolation for DocstringInStub {
|
||||
}
|
||||
|
||||
/// PYI021
|
||||
pub(crate) fn docstring_in_stubs(
|
||||
checker: &Checker,
|
||||
definition: &Definition,
|
||||
docstring: Option<&ExprStringLiteral>,
|
||||
) {
|
||||
pub(crate) fn docstring_in_stubs(checker: &Checker, body: &[Stmt]) {
|
||||
if !matches!(
|
||||
checker.docstring_state(),
|
||||
DocstringState::Expected(
|
||||
ExpectedDocstringKind::Module
|
||||
| ExpectedDocstringKind::Class
|
||||
| ExpectedDocstringKind::Function
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let docstring = docstring_from(body);
|
||||
|
||||
let Some(docstring_range) = docstring.map(ExprStringLiteral::range) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let statements = match definition {
|
||||
Definition::Module(module) => module.python_ast,
|
||||
Definition::Member(member) => member.body(),
|
||||
};
|
||||
|
||||
let edit = if statements.len() == 1 {
|
||||
let edit = if body.len() == 1 {
|
||||
Edit::range_replacement("...".to_string(), docstring_range)
|
||||
} else {
|
||||
Edit::range_deletion(docstring_range)
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(DocstringInStub, docstring_range);
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||
let isolation_level = Checker::isolation(checker.semantic().current_statement_id());
|
||||
let fix = Fix::unsafe_edit(edit).isolate(isolation_level);
|
||||
|
||||
checker
|
||||
.report_diagnostic(DocstringInStub, docstring_range)
|
||||
.set_fix(fix);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI021 [*] Docstrings should not be included in stubs
|
||||
--> PYI021_1.pyi:5:5
|
||||
|
|
||||
4 | def check_isolation_level(mode: int) -> None:
|
||||
5 | """Will report both, but only fix the first.""" # ERROR PYI021
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6 | ... # ERROR PIE790
|
||||
|
|
||||
help: Remove docstring
|
||||
2 |
|
||||
3 |
|
||||
4 | def check_isolation_level(mode: int) -> None:
|
||||
- """Will report both, but only fix the first.""" # ERROR PYI021
|
||||
5 + # ERROR PYI021
|
||||
6 | ... # ERROR PIE790
|
||||
7 |
|
||||
8 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PIE790 [*] Unnecessary `...` literal
|
||||
--> PYI021_1.pyi:6:5
|
||||
|
|
||||
4 | def check_isolation_level(mode: int) -> None:
|
||||
5 | """Will report both, but only fix the first.""" # ERROR PYI021
|
||||
6 | ... # ERROR PIE790
|
||||
| ^^^
|
||||
|
|
||||
help: Remove unnecessary `...`
|
||||
3 |
|
||||
4 | def check_isolation_level(mode: int) -> None:
|
||||
5 | """Will report both, but only fix the first.""" # ERROR PYI021
|
||||
- ... # ERROR PIE790
|
||||
6 + # ERROR PIE790
|
||||
7 |
|
||||
8 |
|
||||
9 | with nullcontext():
|
||||
@@ -58,8 +58,8 @@ enum Method {
|
||||
}
|
||||
|
||||
impl Method {
|
||||
fn is_split(self) -> bool {
|
||||
matches!(self, Method::Split)
|
||||
fn is_rsplit(self) -> bool {
|
||||
matches!(self, Method::RSplit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,65 +201,19 @@ fn split_default(
|
||||
method: Method,
|
||||
settings: &LinterSettings,
|
||||
) -> Option<Expr> {
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
// > no empty strings at the start or end if the string has leading or trailing whitespace.
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
let string_val = str_value.to_str();
|
||||
match max_split.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
if !is_maxsplit_without_separator_fix_enabled(settings) {
|
||||
return None;
|
||||
}
|
||||
Ordering::Greater if !is_maxsplit_without_separator_fix_enabled(settings) => None,
|
||||
Ordering::Greater | Ordering::Equal => {
|
||||
let Ok(max_split) = usize::try_from(max_split) else {
|
||||
return None;
|
||||
};
|
||||
let list_items: Vec<&str> = if method.is_split() {
|
||||
string_val
|
||||
.trim_start_matches(py_unicode_is_whitespace)
|
||||
.splitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
let mut items: Vec<&str> = string_val
|
||||
.trim_end_matches(py_unicode_is_whitespace)
|
||||
.rsplitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
items.reverse();
|
||||
items
|
||||
};
|
||||
let list_items = split_whitespace_with_maxsplit(string_val, max_split, method);
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Behavior for maxsplit = 0 when sep is None:
|
||||
// - If the string is empty or all whitespace, result is [].
|
||||
// - Otherwise:
|
||||
// - " x ".split(maxsplit=0) -> ['x ']
|
||||
// - " x ".rsplit(maxsplit=0) -> [' x']
|
||||
// - "".split(maxsplit=0) -> []
|
||||
// - " ".split(maxsplit=0) -> []
|
||||
let processed_str = if method.is_split() {
|
||||
string_val.trim_start_matches(py_unicode_is_whitespace)
|
||||
} else {
|
||||
string_val.trim_end_matches(py_unicode_is_whitespace)
|
||||
};
|
||||
let list_items: &[_] = if processed_str.is_empty() {
|
||||
&[]
|
||||
} else {
|
||||
&[processed_str]
|
||||
};
|
||||
Some(construct_replacement(
|
||||
list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Less => {
|
||||
let list_items: Vec<&str> = string_val
|
||||
.split(py_unicode_is_whitespace)
|
||||
@@ -367,3 +321,107 @@ const fn py_unicode_is_whitespace(ch: char) -> bool {
|
||||
| '\u{3000}'
|
||||
)
|
||||
}
|
||||
|
||||
struct WhitespaceMaxSplitIterator<'a> {
|
||||
remaining: &'a str,
|
||||
max_split: usize,
|
||||
splits: usize,
|
||||
method: Method,
|
||||
}
|
||||
|
||||
impl<'a> WhitespaceMaxSplitIterator<'a> {
|
||||
fn new(s: &'a str, max_split: usize, method: Method) -> Self {
|
||||
let remaining = match method {
|
||||
Method::Split => s.trim_start_matches(py_unicode_is_whitespace),
|
||||
Method::RSplit => s.trim_end_matches(py_unicode_is_whitespace),
|
||||
};
|
||||
|
||||
Self {
|
||||
remaining,
|
||||
max_split,
|
||||
splits: 0,
|
||||
method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WhitespaceMaxSplitIterator<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remaining.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.splits >= self.max_split {
|
||||
let result = self.remaining;
|
||||
self.remaining = "";
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
self.splits += 1;
|
||||
match self.method {
|
||||
Method::Split => match self.remaining.split_once(py_unicode_is_whitespace) {
|
||||
Some((s, remaining)) => {
|
||||
self.remaining = remaining.trim_start_matches(py_unicode_is_whitespace);
|
||||
Some(s)
|
||||
}
|
||||
None => Some(std::mem::take(&mut self.remaining)),
|
||||
},
|
||||
Method::RSplit => match self.remaining.rsplit_once(py_unicode_is_whitespace) {
|
||||
Some((remaining, s)) => {
|
||||
self.remaining = remaining.trim_end_matches(py_unicode_is_whitespace);
|
||||
Some(s)
|
||||
}
|
||||
None => Some(std::mem::take(&mut self.remaining)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
// > no empty strings at the start or end if the string has leading or trailing whitespace.
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
fn split_whitespace_with_maxsplit(s: &str, max_split: usize, method: Method) -> Vec<&str> {
|
||||
let mut result: Vec<_> = WhitespaceMaxSplitIterator::new(s, max_split, method).collect();
|
||||
if method.is_rsplit() {
|
||||
result.reverse();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Method, split_whitespace_with_maxsplit};
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case(" ", 1, &[])]
|
||||
#[test_case("a b", 1, &["a", "b"])]
|
||||
#[test_case("a b", 2, &["a", "b"])]
|
||||
#[test_case(" a b c d ", 2, &["a", "b", "c d "])]
|
||||
#[test_case(" a b c ", 1, &["a", "b c "])]
|
||||
#[test_case(" x ", 0, &["x "])]
|
||||
#[test_case(" ", 0, &[])]
|
||||
#[test_case("a\u{3000}b", 1, &["a", "b"])]
|
||||
fn test_split_whitespace_with_maxsplit(s: &str, max_split: usize, expected: &[&str]) {
|
||||
let parts = split_whitespace_with_maxsplit(s, max_split, Method::Split);
|
||||
assert_eq!(parts, expected);
|
||||
}
|
||||
|
||||
#[test_case(" ", 1, &[])]
|
||||
#[test_case("a b", 1, &["a", "b"])]
|
||||
#[test_case("a b", 2, &["a", "b"])]
|
||||
#[test_case(" a b c d ", 2, &[" a b", "c", "d"])]
|
||||
#[test_case(" a b c ", 1, &[" a b", "c"])]
|
||||
#[test_case(" x ", 0, &[" x"])]
|
||||
#[test_case(" ", 0, &[])]
|
||||
#[test_case("a\u{3000}b", 1, &["a", "b"])]
|
||||
fn test_rsplit_whitespace_with_maxsplit(s: &str, max_split: usize, expected: &[&str]) {
|
||||
let parts = split_whitespace_with_maxsplit(s, max_split, Method::RSplit);
|
||||
assert_eq!(parts, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,9 @@ impl Violation for SuppressibleException {
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let SuppressibleException { exception } = self;
|
||||
Some(format!("Replace with `contextlib.suppress({exception})`"))
|
||||
Some(format!(
|
||||
"Replace `try`-`except`-`pass` with `with contextlib.suppress({exception}): ...`"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError)` instead of `try`-`except`-`pass
|
||||
9 | | pass
|
||||
| |________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -40,7 +40,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError, OSError)` instead of `try`-`exce
|
||||
17 |
|
||||
18 | # SIM105
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError, OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError, OSError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -71,7 +71,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError, OSError)` instead of `try`-`exce
|
||||
23 |
|
||||
24 | # SIM105
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError, OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError, OSError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -102,7 +102,7 @@ SIM105 [*] Use `contextlib.suppress(BaseException)` instead of `try`-`except`-`p
|
||||
29 |
|
||||
30 | # SIM105
|
||||
|
|
||||
help: Replace with `contextlib.suppress(BaseException)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(BaseException): ...`
|
||||
1 + import contextlib
|
||||
2 + import builtins
|
||||
3 | def foo():
|
||||
@@ -134,7 +134,7 @@ SIM105 [*] Use `contextlib.suppress(a.Error, b.Error)` instead of `try`-`except`
|
||||
35 |
|
||||
36 | # OK
|
||||
|
|
||||
help: Replace with `contextlib.suppress(a.Error, b.Error)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(a.Error, b.Error): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -164,7 +164,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError)` instead of `try`-`except`-`pass
|
||||
88 | | ...
|
||||
| |___________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -195,7 +195,7 @@ SIM105 Use `contextlib.suppress(ValueError, OSError)` instead of `try`-`except`-
|
||||
104 |
|
||||
105 | try:
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError, OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError, OSError): ...`
|
||||
|
||||
SIM105 [*] Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
|
||||
--> SIM105_0.py:117:5
|
||||
@@ -210,7 +210,7 @@ SIM105 [*] Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
|
||||
121 |
|
||||
122 | try: os.makedirs(model_dir);
|
||||
|
|
||||
help: Replace with `contextlib.suppress(OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(OSError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -241,7 +241,7 @@ SIM105 [*] Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
|
||||
125 |
|
||||
126 | try: os.makedirs(model_dir);
|
||||
|
|
||||
help: Replace with `contextlib.suppress(OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(OSError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -271,7 +271,7 @@ SIM105 [*] Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
|
||||
129 | \
|
||||
130 | #
|
||||
|
|
||||
help: Replace with `contextlib.suppress(OSError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(OSError): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -299,7 +299,7 @@ SIM105 [*] Use `contextlib.suppress()` instead of `try`-`except`-`pass`
|
||||
136 | | pass
|
||||
| |________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress()`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
@@ -328,7 +328,7 @@ SIM105 [*] Use `contextlib.suppress(BaseException)` instead of `try`-`except`-`p
|
||||
143 | | pass
|
||||
| |________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress(BaseException)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(BaseException): ...`
|
||||
1 + import contextlib
|
||||
2 | def foo():
|
||||
3 | pass
|
||||
|
||||
@@ -11,7 +11,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError)` instead of `try`-`except`-`pass
|
||||
8 | | pass
|
||||
| |________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError): ...`
|
||||
1 | """Case: There's a random import, so it should add `contextlib` after it."""
|
||||
2 | import math
|
||||
3 + import contextlib
|
||||
|
||||
@@ -11,7 +11,7 @@ SIM105 [*] Use `contextlib.suppress(ValueError)` instead of `try`-`except`-`pass
|
||||
13 | | pass
|
||||
| |________^
|
||||
|
|
||||
help: Replace with `contextlib.suppress(ValueError)`
|
||||
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(ValueError): ...`
|
||||
7 |
|
||||
8 |
|
||||
9 | # SIM105
|
||||
|
||||