Compare commits

..

5 Commits

Author SHA1 Message Date
Alex Waygood
c95cdb3d1d Add regression test and comment for GenericContext::from_type_params 2025-08-15 11:15:42 +01:00
Alex Waygood
7c34ed3374 fix panic 2025-08-14 22:01:21 +01:00
Alex Waygood
5b0a93b3c0 fix memory usage reports 2025-08-14 22:01:21 +01:00
Alex Waygood
cfc46acc46 Do the hard things (with some help from Doug!) 2025-08-14 22:01:21 +01:00
Alex Waygood
671f1358fb Do the easy functions 2025-08-14 22:01:21 +01:00
1106 changed files with 28699 additions and 42441 deletions

View File

@@ -49,7 +49,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build sdist"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
command: sdist
args: --out dist
@@ -79,7 +79,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: x86_64
args: --release --locked --out dist
@@ -121,7 +121,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: aarch64
args: --release --locked --out dist
@@ -177,7 +177,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
@@ -230,7 +230,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -306,7 +306,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
manylinux: auto
@@ -372,7 +372,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -437,7 +437,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2

View File

@@ -715,7 +715,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
args: --out dist
- name: "Test wheel"

View File

@@ -11,7 +11,6 @@ on:
- "crates/ruff_python_parser"
- ".github/workflows/mypy_primer.yaml"
- ".github/workflows/mypy_primer_comment.yaml"
- "scripts/mypy_primer.sh"
- "Cargo.lock"
- "!**.md"

View File

@@ -61,7 +61,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -124,7 +124,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -175,7 +175,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -251,7 +251,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive

View File

@@ -54,9 +54,6 @@ jobs:
- name: Compute diagnostic diff
shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
@@ -66,15 +63,15 @@ jobs:
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
cargo build --bin ty
mv target/debug/ty ty-new
cargo build --release --bin ty
mv target/release/ty ty-new
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
echo "old commit (merge base)"
git rev-list --format=%s --max-count=1 old_commit
cargo build --bin ty
mv target/debug/ty ty-old
cargo build --release --bin ty
mv target/release/ty ty-old
)
(

View File

@@ -1,29 +1,5 @@
# Changelog
## 0.12.10
### Preview features
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
### Bug fixes
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
### Rule changes
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
### Documentation
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
## 0.12.9
### Preview features
@@ -48,31 +24,8 @@
### Other changes
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+ --> unused.py:8:19
|
7 | # Unused, _not_ marked as required (due to the alias).
8 | import pathlib as non_alias
- | ^^^^^^^^^ F401
+ | ^^^^^^^^^
9 |
10 | # Unused, marked as required.
|
- = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
## 0.12.8
### Preview features

110
Cargo.lock generated
View File

@@ -128,9 +128,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.99"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "approx"
@@ -257,12 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.2"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
dependencies = [
"serde",
]
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bitvec"
@@ -411,9 +408,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.45"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -421,9 +418,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.44"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
dependencies = [
"anstream",
"anstyle",
@@ -464,9 +461,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.45"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck",
"proc-macro2",
@@ -1031,16 +1028,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.13"
@@ -1231,9 +1218,9 @@ dependencies = [
[[package]]
name = "glob"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "globset"
@@ -1254,7 +1241,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"ignore",
"walkdir",
]
@@ -1534,7 +1521,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"inotify-sys",
"libc",
]
@@ -1777,9 +1764,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libcst"
@@ -1822,7 +1809,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"libc",
"redox_syscall",
]
@@ -2027,7 +2014,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2039,7 +2026,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2067,7 +2054,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"fsevent-sys",
"inotify",
"kqueue",
@@ -2679,7 +2666,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
]
[[package]]
@@ -2756,13 +2743,13 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode 2.0.1",
"bitflags 2.9.2",
"bitflags 2.9.1",
"cachedir",
"clap",
"clap_complete_command",
@@ -2899,7 +2886,6 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"similar",
"tempfile",
"thiserror 2.0.12",
"tracing",
@@ -3010,11 +2996,11 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.1",
"clap",
"colored 3.0.0",
"fern",
@@ -3120,7 +3106,7 @@ name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"aho-corasick",
"bitflags 2.9.2",
"bitflags 2.9.1",
"compact_str",
"get-size2",
"is-macro",
@@ -3208,7 +3194,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"itertools 0.14.0",
"ruff_python_ast",
"unic-ucd-category",
@@ -3219,7 +3205,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.1",
"bstr",
"compact_str",
"get-size2",
@@ -3244,7 +3230,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"insta",
"is-macro",
"ruff_cache",
@@ -3265,7 +3251,7 @@ dependencies = [
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"unicode-ident",
]
@@ -3349,7 +3335,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3442,7 +3428,7 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys",
@@ -3464,13 +3450,12 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
source = "git+https://github.com/salsa-rs/salsa.git?rev=918d35d873b2b73a0237536144ef4d22e8d57f27#918d35d873b2b73a0237536144ef4d22e8d57f27"
dependencies = [
"boxcar",
"compact_str",
"crossbeam-queue",
"crossbeam-utils",
"erased-serde",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
@@ -3481,7 +3466,6 @@ dependencies = [
"rustc-hash",
"salsa-macro-rules",
"salsa-macros",
"serde",
"smallvec",
"thin-vec",
"tracing",
@@ -3490,12 +3474,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
source = "git+https://github.com/salsa-rs/salsa.git?rev=918d35d873b2b73a0237536144ef4d22e8d57f27#918d35d873b2b73a0237536144ef4d22e8d57f27"
[[package]]
name = "salsa-macros"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
source = "git+https://github.com/salsa-rs/salsa.git?rev=918d35d873b2b73a0237536144ef4d22e8d57f27#918d35d873b2b73a0237536144ef4d22e8d57f27"
dependencies = [
"proc-macro2",
"quote",
@@ -3715,9 +3699,6 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "snapbox"
@@ -3922,9 +3903,6 @@ name = "thin-vec"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
dependencies = [
"serde",
]
[[package]]
name = "thiserror"
@@ -4260,7 +4238,7 @@ dependencies = [
name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
"insta",
"itertools 0.14.0",
"regex",
@@ -4282,7 +4260,6 @@ name = "ty_project"
version = "0.0.0"
dependencies = [
"anyhow",
"bincode 2.0.1",
"camino",
"colored 3.0.0",
"crossbeam",
@@ -4312,7 +4289,6 @@ dependencies = [
"tracing",
"ty_combine",
"ty_python_semantic",
"ty_static",
"ty_vendored",
]
@@ -4321,7 +4297,7 @@ name = "ty_python_semantic"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.1",
"bitvec",
"camino",
"colored 3.0.0",
@@ -4374,7 +4350,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.1",
"crossbeam",
"dunce",
"insta",
@@ -4417,7 +4393,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.1",
"camino",
"colored 3.0.0",
"insta",
@@ -4484,12 +4460,6 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"
@@ -5173,7 +5143,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.1",
]
[[package]]

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.87"
rust-version = "1.86"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -57,8 +57,8 @@ anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
bincode = { version = "2.0.0", features = ["serde"] }
bitflags = { version = "2.5.0", features = ["serde"] }
bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" }
bitvec = { version = "1.0.1", default-features = false, features = [
"alloc",
] }
@@ -126,7 +126,7 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "8.0.0" }
ordermap = { version = "0.5.0", features = ["serde"] }
ordermap = { version = "0.5.0" }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@@ -143,25 +143,24 @@ 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 = "a0e7a06", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "918d35d873b2b73a0237536144ef4d22e8d57f27", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
"persistence",
] }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive", "rc"] }
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.4" }
serde_json = { version = "1.0.142" }
serde_json = { version = "1.0.113" }
serde_test = { version = "1.0.152" }
serde_with = { version = "3.6.0", default-features = false, features = [
"macros",
] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new", "serde"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
snapbox = { version = "0.6.0", features = [
"diff",
"term-svg",
@@ -216,8 +215,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -2 }
# Enabled at the crate level
disallowed_methods = "allow"
# Allowed pedantic lints
char_lit_as_u8 = "allow"
collapsible_else_if = "allow"
@@ -256,7 +253,6 @@ unused_peekable = "warn"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
# Note that we set these explicitly, and these values
# were chosen based on a trade-off between compile times

View File

@@ -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.12.10/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.10/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.9/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.12.10
rev: v0.12.9
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -24,20 +24,3 @@ ignore-interior-mutability = [
# The expression is read-only.
"ruff_python_ast::hashable::HashableExpr",
]
disallowed-methods = [
{ path = "std::env::var", reason = "Use System::env_var instead in ty crates" },
{ path = "std::env::current_dir", reason = "Use System::current_directory instead in ty crates" },
{ path = "std::fs::read_to_string", reason = "Use System::read_to_string instead in ty crates" },
{ path = "std::fs::metadata", reason = "Use System::path_metadata instead in ty crates" },
{ path = "std::fs::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "dunce::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "std::fs::read_dir", reason = "Use System::read_directory instead in ty crates" },
{ path = "std::fs::write", reason = "Use WritableSystem::write_file instead in ty crates" },
{ path = "std::fs::create_dir_all", reason = "Use WritableSystem::create_directory_all instead in ty crates" },
{ path = "std::fs::File::create_new", reason = "Use WritableSystem::create_new_file instead in ty crates" },
# Path methods that have System trait equivalents
{ path = "std::path::Path::exists", reason = "Use System::path_exists instead in ty crates" },
{ path = "std::path::Path::is_dir", reason = "Use System::is_directory instead in ty crates" },
{ path = "std::path::Path::is_file", reason = "Use System::is_file instead in ty crates" },
]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.10"
version = "0.12.9"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -31,7 +31,7 @@ ruff_workspace = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
bincode = { workspace = true }
bincode = { workspace = true, features = ["serde"] }
bitflags = { workspace = true }
cachedir = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }

View File

@@ -5801,32 +5801,3 @@ fn future_annotations_preview_warning() {
",
);
}
#[test]
fn up045_nested_optional_flatten_all() {
let contents = "\
from typing import Optional
nested_optional: Optional[Optional[Optional[str]]] = None
";
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "UP045", "--diff", "--target-version", "py312"])
.arg("-")
.pass_stdin(contents),
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1,2 +1,2 @@
from typing import Optional
-nested_optional: Optional[Optional[Optional[str]]] = None
+nested_optional: str | None = None
----- stderr -----
Would fix 1 error.
",
);
}

View File

@@ -40,7 +40,6 @@ salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
similar = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }

View File

@@ -22,7 +22,6 @@ mod stylesheet;
/// a characteristic is a deficiency. An example of a characteristic that is
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Diagnostic {
/// The actual diagnostic.
///
@@ -501,7 +500,6 @@ impl Diagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct DiagnosticInner {
id: DiagnosticId,
severity: Severity,
@@ -578,7 +576,6 @@ impl Eq for RenderingSortKey<'_> {}
/// another (for a single parent diagnostic) is the order in which they were
/// attached to the diagnostic.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SubDiagnostic {
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
/// pointer-sized.
@@ -688,7 +685,6 @@ impl SubDiagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct SubDiagnosticInner {
severity: SubDiagnosticSeverity,
message: DiagnosticMessage,
@@ -717,7 +713,6 @@ struct SubDiagnosticInner {
/// Messages attached to annotations should also be as brief and specific as
/// possible. Long messages could negative impact the quality of rendering.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Annotation {
/// The span of this annotation, corresponding to some subsequence of the
/// user's input that we want to highlight.
@@ -860,7 +855,6 @@ impl Annotation {
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
@@ -875,7 +869,6 @@ pub enum DiagnosticTag {
///
/// Rules use kebab case, e.g. `no-foo`.
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct LintName(&'static str);
impl LintName {
@@ -888,66 +881,6 @@ impl LintName {
}
}
#[cfg(feature = "serde")]
pub use lint_name_serde::LintRegistryGuard;
#[cfg(feature = "serde")]
mod lint_name_serde {
use super::LintName;
use std::cell::RefCell;
thread_local! {
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
/// but we need a way to retrieve static [`LintName`]s from the lint registry when deserializing.
///
/// Use the [`LintRegistryGuard`] to initialize the thread local before calling into any
/// deserialization code. It ensures that the thread local variable gets cleaned up
/// once deserialization is done (once the guard gets dropped).
static LINT_REGISTRY: RefCell<Option<LintRegistry>> = const { RefCell::new(None) };
}
type LintRegistry = fn(&str) -> Option<LintName>;
/// Guard to safely change the lint registry for the current thread.
#[must_use]
pub struct LintRegistryGuard {
prev_value: Option<LintRegistry>,
}
impl LintRegistryGuard {
pub fn new(registry: LintRegistry) -> Self {
let prev = LINT_REGISTRY.replace(Some(registry));
Self { prev_value: prev }
}
}
impl Drop for LintRegistryGuard {
fn drop(&mut self) {
LINT_REGISTRY.set(self.prev_value.take());
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LintName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name: &str = serde::Deserialize::deserialize(deserializer)?;
LINT_REGISTRY.with_borrow(|registry| {
let registry = registry
.expect("must set the `LintRegistryGuard` when deserializing a `LintName`");
registry(name).ok_or(serde::de::Error::custom(format!(
"invalid `LintName` {name}"
)))
})
}
}
}
impl std::ops::Deref for LintName {
type Target = str;
@@ -976,7 +909,6 @@ impl PartialEq<&str> for LintName {
/// Uniquely identifies the kind of a diagnostic.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticId {
Panic,
@@ -1165,30 +1097,6 @@ impl UnifiedFile {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for UnifiedFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
UnifiedFile::Ty(file) => serde::Serialize::serialize(file, serializer),
// Persistent caching is only used in ty.
UnifiedFile::Ruff(..) => panic!("Ruff files are not persistable"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for UnifiedFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
serde::Deserialize::deserialize(deserializer).map(UnifiedFile::Ty)
}
}
/// A unified wrapper for types that can be converted to a [`SourceCode`].
///
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
@@ -1220,7 +1128,6 @@ impl DiagnosticSource {
/// range isn't present, it semantically implies that the diagnostic refers to
/// the entire file. For example, when the file should be executable but isn't.
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Span {
file: UnifiedFile,
range: Option<TextRange>,
@@ -1299,7 +1206,6 @@ impl From<crate::files::FileRange> for Span {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Severity {
Info,
Warning,
@@ -1335,7 +1241,6 @@ impl Severity {
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
/// deleted and the two combined again.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SubDiagnosticSeverity {
Help,
Info,
@@ -1389,10 +1294,6 @@ pub struct DisplayDiagnosticConfig {
hide_severity: bool,
/// Whether to show the availability of a fix in a diagnostic.
show_fix_status: bool,
/// Whether to show the diff for an available fix after the main diagnostic.
///
/// This currently only applies to `DiagnosticFormat::Full`.
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
}
@@ -1440,14 +1341,6 @@ impl DisplayDiagnosticConfig {
}
}
/// Whether to show a diff for an available fix after the main diagnostic.
pub fn show_fix_diff(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
show_fix_diff: yes,
..self
}
}
/// Set the lowest fix applicability that should be shown.
///
/// In other words, an applicability of `Safe` (the default) would suppress showing fixes or fix
@@ -1471,7 +1364,6 @@ impl Default for DisplayDiagnosticConfig {
preview: false,
hide_severity: false,
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
}
}
@@ -1584,7 +1476,6 @@ impl std::fmt::Display for ConciseMessage<'_> {
/// a blanket trait implementation for `IntoDiagnosticMessage` for
/// anything that implements `std::fmt::Display`.
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
@@ -1648,11 +1539,7 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
///
/// For Ruff rules this means the noqa code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct SecondaryCode(String);
impl SecondaryCode {

View File

@@ -1,17 +1,7 @@
use std::borrow::Cow;
use std::num::NonZeroUsize;
use anstyle::Style;
use similar::{ChangeTag, TextDiff};
use ruff_annotate_snippets::Renderer as AnnotateRenderer;
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::OneIndexed;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::diagnostic::render::{FileResolver, Resolved};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, stylesheet::DiagnosticStylesheet};
pub(super) struct FullRenderer<'a> {
resolver: &'a dyn FileResolver,
@@ -58,199 +48,12 @@ impl<'a> FullRenderer<'a> {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
}
writeln!(f)?;
if self.config.show_fix_diff {
if let Some(diff) = Diff::from_diagnostic(diag, &stylesheet, self.resolver) {
writeln!(f, "{diff}")?;
}
}
}
Ok(())
}
}
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the `Edit` because diff calculation is expensive.
struct Diff<'a> {
fix: &'a Fix,
diagnostic_source: DiagnosticSource,
stylesheet: &'a DiagnosticStylesheet,
}
impl<'a> Diff<'a> {
fn from_diagnostic(
diagnostic: &'a Diagnostic,
stylesheet: &'a DiagnosticStylesheet,
resolver: &'a dyn FileResolver,
) -> Option<Diff<'a>> {
Some(Diff {
fix: diagnostic.fix()?,
diagnostic_source: diagnostic
.primary_span_ref()?
.file
.diagnostic_source(resolver),
stylesheet,
})
}
}
impl std::fmt::Display for Diff<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let source_code = self.diagnostic_source.as_source_code();
let source_text = source_code.text();
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(source_text.len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&source_text[usize::from(last_end)..]);
let diff = TextDiff::from_lines(source_text, &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
// TODO(brent) `stylesheet.separator` is cyan rather than blue, as we had before. I think
// we're getting rid of this soon anyway, so I didn't think it was worth adding another
// style to the stylesheet temporarily. The color doesn't appear at all in the snapshot
// tests, which is the only place these are currently used.
writeln!(f, " {}", fmt_styled(message, self.stylesheet.separator))?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag(), self.stylesheet);
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = show_nonprinting(&value);
if emphasized {
write!(
f,
"{}",
fmt_styled(line_style.apply_to(&value), self.stylesheet.underline)
)?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
Ok(())
}
}
struct LineStyle {
style: Style,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> impl std::fmt::Display {
fmt_styled(input, self.style)
}
fn from(value: ChangeTag, stylesheet: &DiagnosticStylesheet) -> LineStyle {
match value {
ChangeTag::Equal => LineStyle {
style: stylesheet.none,
},
ChangeTag::Delete => LineStyle {
style: stylesheet.deletion,
},
ChangeTag::Insert => LineStyle {
style: stylesheet.insertion,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl std::fmt::Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
fn show_nonprinting(s: &str) -> Cow<'_, str> {
if s.find(['\x07', '\x08', '\x1b', '\x7f']).is_some() {
Cow::Owned(
s.replace('\x07', "")
.replace('\x08', "")
.replace('\x1b', "")
.replace('\x7f', ""),
)
} else {
Cow::Borrowed(s)
}
}
#[cfg(test)]
mod tests {
use ruff_diagnostics::Applicability;

View File

@@ -40,12 +40,9 @@ pub struct DiagnosticStylesheet {
pub(crate) help: Style,
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) underline: Style,
pub(crate) none: Style,
pub(crate) separator: Style,
pub(crate) secondary_code: Style,
pub(crate) insertion: Style,
pub(crate) deletion: Style,
}
impl Default for DiagnosticStylesheet {
@@ -66,12 +63,9 @@ impl DiagnosticStylesheet {
help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
line_no: bright_blue.effects(Effects::BOLD),
emphasis: Style::new().effects(Effects::BOLD),
underline: Style::new().effects(Effects::UNDERLINE),
none: Style::new(),
separator: AnsiColor::Cyan.on_default(),
secondary_code: AnsiColor::Red.on_default().effects(Effects::BOLD),
insertion: AnsiColor::Green.on_default(),
deletion: AnsiColor::Red.on_default(),
}
}
@@ -84,12 +78,9 @@ impl DiagnosticStylesheet {
help: Style::new(),
line_no: Style::new(),
emphasis: Style::new(),
underline: Style::new(),
none: Style::new(),
separator: Style::new(),
secondary_code: Style::new(),
insertion: Style::new(),
deletion: Style::new(),
}
}
}

View File

@@ -9,17 +9,7 @@ use crate::system::file_time_now;
/// * The last modification time of the file.
/// * The hash of the file's content.
/// * The revision as it comes from an external system, for example the LSP.
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
pub struct FileRevision(u128);
impl FileRevision {

View File

@@ -14,7 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
use crate::file_revision::FileRevision;
use crate::files::file_root::FileRoots;
use crate::files::private::FileStatus;
use crate::system::{FileType, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::vendored::{VendoredPath, VendoredPathBuf};
use crate::{Db, FxDashMap, vendored};
@@ -139,7 +139,6 @@ impl Files {
};
tracing::trace!("Adding vendored file `{}`", path);
let file = File::builder(FilePath::Vendored(path.to_path_buf()))
.permissions(Some(0o444))
.revision(metadata.revision())
@@ -201,15 +200,7 @@ impl Files {
let mut roots = self.inner.roots.write().unwrap();
let absolute = SystemPath::absolute(path, db.system().current_directory());
let (Ok(root) | Err(root)) = roots.try_add(db, absolute, |absolute| {
FileRoot::builder(absolute, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db)
});
root
roots.try_add(db, absolute, kind)
}
/// Updates the revision of the root for `path`.
@@ -268,51 +259,6 @@ impl Files {
root.set_revision(db).to(FileRevision::now());
}
}
/// Seed the files with an existing [`File`] instance.
pub fn seed(&self, file: File, db: &dyn Db) {
let seeded = match file.path(db) {
FilePath::System(path) => self
.inner
.system_by_path
.insert(path.clone(), file)
.is_none(),
FilePath::SystemVirtual(path) => self
.inner
.system_virtual_by_path
.insert(path.clone(), VirtualFile(file))
.is_none(),
FilePath::Vendored(path) => self
.inner
.vendored_by_path
.insert(path.clone(), file)
.is_none(),
};
// Recreating a `File` input means the persisted queries depending on that file
// will be invalidated.
assert!(
seeded,
"unexpected `File` input recreated for path `{}`",
file.path(db)
);
}
/// Seed the files with an existing [`FileRoot`] instance.
pub fn seed_root(&self, root: FileRoot, db: &dyn Db) {
let mut roots = self.inner.roots.write().unwrap();
let seeded = roots
.try_add(db, root.path(db).to_path_buf(), |_| root)
.is_ok();
// Recreating a `FileRoot` input means the persisted queries depending on that file
// root will be invalidated.
assert!(
seeded,
"unexpected `FileRoot` input recreated for path `{}`",
root.path(db)
);
}
}
impl fmt::Debug for Files {
@@ -344,7 +290,7 @@ impl std::panic::RefUnwindSafe for Files {}
/// # Ordering
/// Ordering is based on the file's salsa-assigned id and not on its values.
/// The id may change between runs.
#[salsa::input(persist, heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct File {
/// The path of the file (immutable).
@@ -468,15 +414,6 @@ impl File {
}
}
/// Loads all existing [`File`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<File> {
// TODO: Prune deleted paths.
File::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
/// Private method providing the implementation for [`Self::sync_path`] and [`Self::sync`] for
/// system paths.
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
@@ -585,17 +522,7 @@ impl VirtualFile {
// The types in here need to be public because they're salsa ingredients but we
// don't want them to be publicly accessible. That's why we put them into a private module.
mod private {
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
pub enum FileStatus {
/// The file exists.
#[default]
@@ -609,16 +536,6 @@ mod private {
}
}
impl From<FileType> for FileStatus {
fn from(value: FileType) -> Self {
match value {
FileType::File => FileStatus::Exists,
FileType::Symlink => FileStatus::Exists,
FileType::Directory => FileStatus::IsADirectory,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FileError {
IsADirectory,

View File

@@ -16,7 +16,7 @@ use crate::system::{SystemPath, SystemPathBuf};
/// The main usage of file roots is to determine a file's durability. But it can also be used
/// to make a salsa query dependent on whether a file in a root has changed without writing any
/// manual invalidation logic.
#[salsa::input(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(debug, heap_size=ruff_memory_usage::heap_size)]
pub struct FileRoot {
/// The path of a root is guaranteed to never change.
#[returns(deref)]
@@ -35,20 +35,9 @@ impl FileRoot {
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
self.kind_at_time_of_creation(db).durability()
}
/// Loads all existing [`FileRoot`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<FileRoot> {
// TODO: Prune deleted paths.
FileRoot::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
}
#[derive(
Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
pub enum FileRootKind {
/// The root of a project.
Project,
@@ -58,7 +47,7 @@ pub enum FileRootKind {
}
impl FileRootKind {
pub const fn durability(self) -> Durability {
const fn durability(self) -> Durability {
match self {
FileRootKind::Project => Durability::LOW,
FileRootKind::LibrarySearchPath => Durability::HIGH,
@@ -73,34 +62,34 @@ pub(super) struct FileRoots {
}
impl FileRoots {
/// Tries to add a new root for `path`.
/// Tries to add a new root for `path` and returns the root.
///
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
///
/// Returns `Ok(root)` if the `FileRoot` was successfully added, and returns `Err(root)` with
/// the previous root if one already existed at that path.
pub(super) fn try_add(
&mut self,
db: &dyn Db,
path: SystemPathBuf,
create_root: impl FnOnce(SystemPathBuf) -> FileRoot,
) -> Result<FileRoot, FileRoot> {
kind: FileRootKind,
) -> FileRoot {
// SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters.
let normalized_path = path.as_std_path().to_slash().unwrap();
if let Ok(existing) = self.by_path.at(&normalized_path) {
// Only if it is an exact match
if existing.value.path(db) == &*path {
return Err(*existing.value);
return *existing.value;
}
}
// Normalize the path to use `/` separators and escape the '{' and '}' characters,
// which `matchit` uses for routing parameters.
// normalize the path to use `/` separators and escape the '{' and '}' characters,
// which matchit uses for routing parameters
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
// Insert a new source root
let root = create_root(path);
let root = FileRoot::builder(path, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db);
// Insert a path that matches the root itself
self.by_path.insert(route.clone(), root).unwrap();
@@ -111,7 +100,7 @@ impl FileRoots {
self.by_path.insert(route, root).unwrap();
self.roots.push(root);
Ok(root)
root
}
/// Returns the closest root for `path` or `None` if no root contains `path`.

View File

@@ -11,9 +11,7 @@ use std::fmt::{Display, Formatter};
/// * a file stored on the [host system](crate::system::System).
/// * a virtual file stored on the [host system](crate::system::System).
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
#[derive(
Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub enum FilePath {
/// Path to a file on the [host system](crate::system::System).
System(SystemPathBuf),

View File

@@ -1,8 +1,3 @@
#![warn(
clippy::disallowed_methods,
reason = "Prefer System trait methods over std methods"
)]
use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
@@ -70,10 +65,6 @@ pub trait Db: salsa::Database {
/// to process work in parallel. For example, to index a directory or checking the files of a project.
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
/// watching the files for changes.
#[expect(
clippy::disallowed_methods,
reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
)]
pub fn max_parallelism() -> NonZeroUsize {
std::env::var(EnvVars::TY_MAX_PARALLELISM)
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))

View File

@@ -92,14 +92,14 @@ impl ParsedModule {
self.inner.store(None);
}
/// Returns the pointer address of this [`ParsedModule`].
/// Returns a pointer for this [`ParsedModule`].
///
/// The pointer uniquely identifies the module within the current Salsa revision,
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
pub fn addr(&self) -> usize {
pub fn as_ptr(&self) -> *const () {
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
// `Arc` within the `ArcSwap` may change.
Arc::as_ptr(&self.inner).addr()
Arc::as_ptr(&self.inner).cast()
}
}
@@ -202,13 +202,9 @@ mod indexed {
/// Returns the node at the given index.
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
let index = index
.as_u32()
.expect("attempted to access uninitialized `NodeIndex`");
// Note that this method restores the correct lifetime: the nodes are valid for as
// long as the reference to `IndexedModule` is alive.
self.index[index as usize]
self.index[index.as_usize()]
}
}
@@ -224,7 +220,7 @@ mod indexed {
T: HasNodeIndex + std::fmt::Debug,
AnyRootNodeRef<'a>: From<&'a T>,
{
node.node_index().set(NodeIndex::from(self.index));
node.node_index().set(self.index);
self.nodes.push(AnyRootNodeRef::from(node));
self.index += 1;
}

View File

@@ -148,16 +148,7 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(
Debug,
thiserror::Error,
PartialEq,
Eq,
Clone,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),

View File

@@ -46,7 +46,7 @@ pub type Result<T> = std::io::Result<T>;
/// * File watching isn't supported.
///
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
pub trait System: Debug + Sync + Send {
pub trait System: Debug {
/// Reads the metadata of the file or directory at `path`.
///
/// This function will traverse symbolic links to query information about the destination file.
@@ -66,9 +66,6 @@ pub trait System: Debug + Sync + Send {
/// See [dunce::canonicalize] for more information.
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
/// Reads the content of the file at `path` into a bytes buffer.
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>>;
/// Reads the content of the file at `path` into a [`String`].
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
@@ -200,8 +197,6 @@ pub trait System: Debug + Sync + Send {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn dyn_clone(&self) -> Box<dyn System>;
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
@@ -245,7 +240,7 @@ pub trait WritableSystem: System {
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
/// Writes the given content to the file at the given path.
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
/// Creates a directory at `path` as well as any intermediate directories.
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
@@ -281,7 +276,7 @@ pub trait WritableSystem: System {
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
// the cache.
self.create_new_file(&cache_path)?;
self.write_file(&cache_path, contents.as_bytes())?;
self.write_file(&cache_path, &contents)?;
Ok(Some(cache_path))
}

View File

@@ -114,8 +114,8 @@ impl MemoryFileSystem {
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
}
pub fn read_to_end(&self, path: impl AsRef<SystemPath>) -> Result<Vec<u8>> {
fn read_to_end(fs: &MemoryFileSystem, path: &SystemPath) -> Result<Vec<u8>> {
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
fn read_to_string(fs: &MemoryFileSystem, path: &SystemPath) -> Result<String> {
let by_path = fs.inner.by_path.read().unwrap();
let normalized = fs.normalize_path(path);
@@ -127,32 +127,19 @@ impl MemoryFileSystem {
}
}
read_to_end(self, path.as_ref())
}
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
self.read_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
}
pub(crate) fn read_virtual_path_to_end(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<Vec<u8>> {
let virtual_files = self.inner.virtual_files.read().unwrap();
let file = virtual_files
.get(&path.as_ref().to_path_buf())
.ok_or_else(not_found)?;
Ok(file.content.clone())
read_to_string(self, path.as_ref())
}
pub(crate) fn read_virtual_path_to_string(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<String> {
self.read_virtual_path_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
let virtual_files = self.inner.virtual_files.read().unwrap();
let file = virtual_files
.get(&path.as_ref().to_path_buf())
.ok_or_else(not_found)?;
Ok(file.content.clone())
}
pub fn exists(&self, path: &SystemPath) -> bool {
@@ -174,7 +161,7 @@ impl MemoryFileSystem {
match by_path.entry(normalized) {
btree_map::Entry::Vacant(entry) => {
entry.insert(Entry::File(File {
content: Vec::new(),
content: String::new(),
last_modified: file_time_now(),
}));
@@ -190,17 +177,13 @@ impl MemoryFileSystem {
/// Stores a new file in the file system.
///
/// The operation overrides the content for an existing file with the same normalized `path`.
pub fn write_file(
&self,
path: impl AsRef<SystemPath>,
content: impl Into<Vec<u8>>,
) -> Result<()> {
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
let mut by_path = self.inner.by_path.write().unwrap();
let normalized = self.normalize_path(path.as_ref());
let file = get_or_create_file(&mut by_path, &normalized)?;
file.content = content.into();
file.content = content.to_string();
file.last_modified = file_time_now();
Ok(())
@@ -231,7 +214,7 @@ impl MemoryFileSystem {
pub fn write_file_all(
&self,
path: impl AsRef<SystemPath>,
content: impl Into<Vec<u8>>,
content: impl ToString,
) -> Result<()> {
let path = path.as_ref();
@@ -245,23 +228,19 @@ impl MemoryFileSystem {
/// Stores a new virtual file in the file system.
///
/// The operation overrides the content for an existing virtual file with the same `path`.
pub fn write_virtual_file(
&self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
let path = path.as_ref();
let mut virtual_files = self.inner.virtual_files.write().unwrap();
match virtual_files.entry(path.to_path_buf()) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(File {
content: content.into(),
content: content.to_string(),
last_modified: file_time_now(),
});
}
std::collections::hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().content = content.into();
entry.get_mut().content = content.to_string();
}
}
}
@@ -489,7 +468,7 @@ impl Entry {
#[derive(Debug)]
struct File {
content: Vec<u8>,
content: String,
last_modified: FileTime,
}
@@ -554,7 +533,7 @@ fn get_or_create_file<'a>(
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
Entry::File(File {
content: Vec::new(),
content: String::new(),
last_modified: file_time_now(),
})
});

View File

@@ -1,5 +1,3 @@
#![allow(clippy::disallowed_methods)]
use super::walk_directory::{
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
WalkDirectoryVisitorBuilder, WalkState,
@@ -93,10 +91,6 @@ impl System for OsSystem {
})
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
std::fs::read(path.as_std_path())
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
std::fs::read_to_string(path.as_std_path())
}
@@ -261,10 +255,6 @@ impl System for OsSystem {
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
std::env::var(name)
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl OsSystem {
@@ -361,7 +351,7 @@ impl WritableSystem for OsSystem {
std::fs::File::create_new(path).map(drop)
}
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
std::fs::write(path.as_std_path(), content)
}

View File

@@ -762,17 +762,7 @@ impl SystemVirtualPath {
}
/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
#[derive(
Eq,
PartialEq,
Clone,
Hash,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord, get_size2::GetSize)]
pub struct SystemVirtualPathBuf(String);
impl SystemVirtualPathBuf {

View File

@@ -75,10 +75,6 @@ impl System for TestSystem {
self.system().canonicalize_path(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.system().read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.system().read_to_string(path)
}
@@ -150,10 +146,6 @@ impl System for TestSystem {
fn case_sensitivity(&self) -> CaseSensitivity {
self.system().case_sensitivity()
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl Default for TestSystem {
@@ -169,7 +161,7 @@ impl WritableSystem for TestSystem {
self.system().create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.system().write_file(path, content)
}
@@ -189,9 +181,7 @@ pub trait DbWithWritableSystem: Db + Sized {
/// Writes the content of the given file and notifies the Db about the change.
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
let path = path.as_ref();
let content = content.as_ref();
match self.writable_system().write_file(path, content.as_bytes()) {
match self.writable_system().write_file(path, content.as_ref()) {
Ok(()) => {
File::sync_path(self, path);
Ok(())
@@ -204,8 +194,7 @@ pub trait DbWithWritableSystem: Db + Sized {
File::sync_path(self, ancestor);
}
self.writable_system()
.write_file(path, content.as_bytes())?;
self.writable_system().write_file(path, content.as_ref())?;
File::sync_path(self, path);
Ok(())
@@ -250,14 +239,8 @@ pub trait DbWithTestSystem: Db + Sized {
///
/// ## Panics
/// If the db isn't using the [`InMemorySystem`].
fn write_virtual_file(
&mut self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
let path = path.as_ref();
let content = content.into();
self.test_system()
.memory_file_system()
.write_virtual_file(path, content);
@@ -335,10 +318,6 @@ impl System for InMemorySystem {
self.memory_fs.canonicalize(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.memory_fs.read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.memory_fs.read_to_string(path)
}
@@ -415,13 +394,6 @@ impl System for InMemorySystem {
fn case_sensitivity(&self) -> CaseSensitivity {
CaseSensitivity::CaseSensitive
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(Self {
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
memory_fs: self.memory_fs.clone(),
})
}
}
impl WritableSystem for InMemorySystem {
@@ -429,7 +401,7 @@ impl WritableSystem for InMemorySystem {
self.memory_fs.create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.memory_fs.write_file(path, content)
}

View File

@@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
}
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct VendoredPathBuf(Utf8PathBuf);
impl get_size2::GetSize for VendoredPathBuf {

View File

@@ -14,11 +14,8 @@ license = { workspace = true }
doctest = false
[dependencies]
ruff_text_size = { workspace = true, features = ["get-size"] }
ruff_text_size = { workspace = true }
get-size2 = { workspace = true }
is-macro = { workspace = true }
serde = { workspace = true, optional = true, features = [] }
[features]
serde = ["dep:serde", "ruff_text_size/serde"]

View File

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

View File

@@ -166,7 +166,3 @@ r"""first
print("S\x1cP\x1dL\x1eI\x1fT".split())
print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
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"]

View File

@@ -13,11 +13,3 @@ Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
fd = os.open(".", os.O_RDONLY)
os.symlink("source.txt", "link.txt", dir_fd=fd) # Ok: dir_fd is not supported by pathlib
os.close(fd)
os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")

View File

@@ -106,22 +106,4 @@ os.replace("src", "dst", src_dir_fd=1)
os.replace("src", "dst", dst_dir_fd=2)
os.getcwd()
os.getcwdb()
os.mkdir(path="directory")
os.mkdir(
# comment 1
"directory",
mode=0o777
)
os.mkdir("directory", mode=0o777, dir_fd=1)
os.makedirs("name", 0o777, exist_ok=False)
os.makedirs("name", 0o777, False)
os.makedirs(name="name", mode=0o777, exist_ok=False)
os.makedirs("name", unknown_kwarg=True)
os.getcwdb()

View File

@@ -1,4 +0,0 @@
"""Hello, world!"""\
\
x = 1; y = 2

View File

@@ -1,18 +0,0 @@
from __future__ import nested_scopes, generators
from __future__ import with_statement, unicode_literals
from __future__ import absolute_import, division
from __future__ import generator_stop
from __future__ import print_function, nested_scopes, generator_stop
print(with_statement)
generators = 1
class Foo():
def boo(self):
print(division)
__all__ = ["print_function", "generator_stop"]

View File

@@ -69,10 +69,3 @@ a7: OptionalTE[typing.NamedTuple] = None
a8: typing_extensions.Optional[typing.NamedTuple] = None
a9: "Optional[NamedTuple]" = None
a10: Optional[NamedTupleTE] = None
# Test for: https://github.com/astral-sh/ruff/issues/19746
# Nested Optional types should be flattened
nested_optional: Optional[Optional[str]] = None
nested_optional_typing: typing.Optional[Optional[int]] = None
triple_nested_optional: Optional[Optional[Optional[str]]] = None

View File

@@ -118,10 +118,3 @@ def func():
return lambda: value
defaultdict(constant_factory("<missing>"))
def func():
defaultdict(default_factory=t"") # OK
def func():
defaultdict(default_factory=t"hello") # OK

View File

@@ -102,8 +102,3 @@ deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK
# https://github.com/astral-sh/ruff/issues/19951
deque(t"")
deque(t"" t"")
deque(t"{""}") # OK

View File

@@ -1,11 +1,10 @@
use ruff_python_ast::PythonVersion;
use ruff_python_semantic::{Binding, ScopeKind};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
pyflakes, pylint, pyupgrade, ruff,
pyflakes, pylint, ruff,
};
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
@@ -46,7 +45,6 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
Rule::UnusedStaticMethodArgument,
Rule::UnusedUnpackedVariable,
Rule::UnusedVariable,
Rule::UnnecessaryFutureImport,
]) {
return;
}
@@ -226,11 +224,6 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
if checker.is_rule_enabled(Rule::UnusedImport) {
pyflakes::rules::unused_import(checker, scope);
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
pyupgrade::rules::unnecessary_future_import(checker, scope);
}
}
if checker.is_rule_enabled(Rule::ImportPrivateName) {
pylint::rules::import_private_name(checker, scope);

View File

@@ -1039,6 +1039,8 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
}
if checker.any_rule_enabled(&[
Rule::OsMkdir,
Rule::OsMakedirs,
Rule::OsStat,
Rule::OsPathJoin,
Rule::OsPathSplitext,
@@ -1118,15 +1120,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::OsPathSamefile) {
flake8_use_pathlib::rules::os_path_samefile(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMkdir) {
flake8_use_pathlib::rules::os_mkdir(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMakedirs) {
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsSymlink) {
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(
checker, call, segments,

View File

@@ -728,6 +728,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pylint::rules::non_ascii_module_import(checker, alias);
}
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
if let Some("__future__") = module {
pyupgrade::rules::unnecessary_future_import(checker, stmt, names);
}
}
}
if checker.is_rule_enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_import(checker, stmt);
}

View File

@@ -921,8 +921,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-use-pathlib
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsChmod),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMakedirs),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRename),
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReplace),
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
@@ -954,7 +954,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsSymlink),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),

View File

@@ -63,9 +63,9 @@ impl<'a> Insertion<'a> {
return Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";");
}
// While the first token after the docstring is a continuation character (i.e. "\"), advance
// additional rows to prevent inserting in the same logical line.
while match_continuation(locator.after(location)).is_some() {
// If the first token after the docstring is a continuation character (i.e. "\"), advance
// an additional row to prevent inserting in the same logical line.
if match_continuation(locator.after(location)).is_some() {
location = locator.full_line_end(location);
}
@@ -379,17 +379,6 @@ mod tests {
Insertion::own_line("", TextSize::from(22), "\n")
);
let contents = r#"
"""Hello, world!"""\
\
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::own_line("", TextSize::from(24), "\n")
);
let contents = r"
x = 1
"

View File

@@ -0,0 +1,202 @@
use std::fmt::{Display, Formatter};
use std::num::NonZeroUsize;
use colored::{Color, ColoredString, Colorize, Styles};
use similar::{ChangeTag, TextDiff};
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::{OneIndexed, SourceFile};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::text_helpers::ShowNonprinting;
use crate::{Applicability, Fix};
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the [`Edit`] because diff calculation is expensive.
pub(super) struct Diff<'a> {
fix: &'a Fix,
source_code: &'a SourceFile,
}
impl<'a> Diff<'a> {
pub(crate) fn from_message(message: &'a Diagnostic) -> Option<Diff<'a>> {
message.fix().map(|fix| Diff {
source_code: message.expect_ruff_source_file(),
fix,
})
}
}
impl Display for Diff<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(self.source_code.source_text().len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(
self.source_code
.slice(TextRange::new(last_end, edit.start())),
);
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&self.source_code.source_text()[usize::from(last_end)..]);
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
writeln!(f, " {}", message.blue())?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with =
calculate_print_width(OneIndexed::from_zero_indexed(largest_new.max(largest_old)));
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag());
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
line_style.apply_to(sign).bold()
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = value.show_nonprinting();
if emphasized {
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
Ok(())
}
}
struct LineStyle {
fgcolor: Option<Color>,
style: Option<Styles>,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> ColoredString {
let mut colored = ColoredString::from(input);
if let Some(color) = self.fgcolor {
colored = colored.color(color);
}
if let Some(style) = self.style {
match style {
Styles::Clear => colored.clear(),
Styles::Bold => colored.bold(),
Styles::Dimmed => colored.dimmed(),
Styles::Underline => colored.underline(),
Styles::Reversed => colored.reversed(),
Styles::Italic => colored.italic(),
Styles::Blink => colored.blink(),
Styles::Hidden => colored.hidden(),
Styles::Strikethrough => colored.strikethrough(),
}
} else {
colored
}
}
}
impl From<ChangeTag> for LineStyle {
fn from(value: ChangeTag) -> Self {
match value {
ChangeTag::Equal => LineStyle {
fgcolor: None,
style: Some(Styles::Dimmed),
},
ChangeTag::Delete => LineStyle {
fgcolor: Some(Color::Red),
style: None,
},
ChangeTag::Insert => LineStyle {
fgcolor: Some(Color::Green),
style: None,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl Display for Line {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
/// Calculate the length of the string representation of `value`
pub(super) fn calculate_print_width(mut value: OneIndexed) -> NonZeroUsize {
const TEN: OneIndexed = OneIndexed::from_zero_indexed(9);
let mut width = OneIndexed::ONE;
while value >= TEN {
value = OneIndexed::new(value.get() / 10).unwrap_or(OneIndexed::MIN);
width = width.checked_add(1).unwrap();
}
width
}

View File

@@ -10,6 +10,7 @@ use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, OneIndexed};
use crate::fs::relativize_path;
use crate::message::diff::calculate_print_width;
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
@@ -52,8 +53,8 @@ impl Emitter for GroupedEmitter {
max_column_length = max_column_length.max(message.start_location.column);
}
let row_length = max_row_length.digits();
let column_length = max_column_length.digits();
let row_length = calculate_print_width(max_row_length);
let column_length = calculate_print_width(max_column_length);
// Print the filename.
writeln!(writer, "{}:", relativize_path(&*filename).underline())?;
@@ -130,7 +131,8 @@ impl Display for DisplayGroupedMessage<'_> {
write!(
f,
" {row_padding}",
row_padding = " ".repeat(self.row_length.get() - start_location.line.digits().get())
row_padding = " "
.repeat(self.row_length.get() - calculate_print_width(start_location.line).get())
)?;
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
@@ -157,8 +159,9 @@ impl Display for DisplayGroupedMessage<'_> {
f,
"{row}{sep}{col}{col_padding} {code_and_body}",
sep = ":".cyan(),
col_padding =
" ".repeat(self.column_length.get() - start_location.column.digits().get()),
col_padding = " ".repeat(
self.column_length.get() - calculate_print_width(start_location.column).get()
),
code_and_body = RuleCodeAndBody {
message,
show_fix_status: self.show_fix_status,

View File

@@ -21,6 +21,7 @@ pub use text::TextEmitter;
use crate::Fix;
use crate::registry::Rule;
mod diff;
mod github;
mod gitlab;
mod grouped;

View File

@@ -1,19 +1,23 @@
use std::io::Write;
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
};
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
pub struct TextEmitter {
/// Whether to show the diff of a fix, for diagnostics that have a fix.
///
/// Note that this is not currently exposed in the CLI (#7352) and is only used in tests.
show_fix_diff: bool,
config: DisplayDiagnosticConfig,
}
impl Default for TextEmitter {
fn default() -> Self {
Self {
show_fix_diff: false,
config: DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Concise)
.hide_severity(true)
@@ -31,7 +35,7 @@ impl TextEmitter {
#[must_use]
pub fn with_show_fix_diff(mut self, show_fix_diff: bool) -> Self {
self.config = self.config.show_fix_diff(show_fix_diff);
self.show_fix_diff = show_fix_diff;
self
}
@@ -73,11 +77,15 @@ impl Emitter for TextEmitter {
diagnostics: &[Diagnostic],
context: &EmitterContext,
) -> anyhow::Result<()> {
write!(
writer,
"{}",
DisplayDiagnostics::new(context, &self.config, diagnostics)
)?;
for message in diagnostics {
write!(writer, "{}", message.display(context, &self.config))?;
if self.show_fix_diff {
if let Some(diff) = Diff::from_message(message) {
writeln!(writer, "{diff}")?;
}
}
}
Ok(())
}

View File

@@ -159,21 +159,6 @@ pub(crate) const fn is_fix_os_getcwd_enabled(settings: &LinterSettings) -> bool
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_mkdir_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/20009
pub(crate) const fn is_fix_os_symlink_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
@@ -245,8 +230,3 @@ pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterS
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19851
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -137,7 +137,7 @@ impl AutoPythonType {
let expr = Expr::Name(ast::ExprName {
id: Name::from(binding),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
Some((expr, vec![no_return_edit]))
@@ -204,7 +204,7 @@ fn type_expr(python_type: PythonType) -> Option<Expr> {
Expr::Name(ast::ExprName {
id: name.into(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
})
}

View File

@@ -485,6 +485,9 @@ impl Violation for MissingReturnTypeClassMethod {
/// Use instead:
///
/// ```python
/// from typing import Any
///
///
/// def foo(x: int): ...
/// ```
///

View File

@@ -52,13 +52,13 @@ impl AlwaysFixableViolation for AssertFalse {
fn assertion_error(msg: Option<&Expr>) -> Stmt {
Stmt::Raise(ast::StmtRaise {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
exc: Some(Box::new(Expr::Call(ast::ExprCall {
func: Box::new(Expr::Name(ast::ExprName {
id: "AssertionError".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
arguments: Arguments {
args: if let Some(msg) = msg {
@@ -68,10 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))),
cause: None,
})

View File

@@ -113,7 +113,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
}
.into()

View File

@@ -53,11 +53,11 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
attr: Identifier::new(name.to_string(), TextRange::default()),
ctx: ExprContext::Store,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})],
value: Box::new(value.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
generator.stmt(&stmt)
}

View File

@@ -10,7 +10,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
use crate::{assert_diagnostics, settings};
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
@@ -31,24 +31,19 @@ mod tests {
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
let rules = vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
];
let settings_before = settings::LinterSettings::for_rules(rules.clone());
let settings_after = settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(rules)
};
assert_diagnostics_diff!(
snapshot,
let snapshot = format!("preview__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_commas").join(path).as_path(),
&settings_before,
&settings_after
);
&settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
])
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -0,0 +1,33 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
invalid-syntax: Starred expression cannot be used here
--> COM81_syntax_error.py:3:5
|
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | (
3 | *args
| ^^^^^
4 | )
|
invalid-syntax: Type parameter list cannot be empty
--> COM81_syntax_error.py:6:9
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
COM819 Trailing comma prohibited
--> COM81_syntax_error.py:6:38
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
help: Remove trailing comma

View File

@@ -1,136 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 6
--- Added ---
COM812 [*] Trailing comma missing
--> COM81.py:655:6
|
654 | type X[
655 | T
| ^
656 | ] = T
657 | def f[
|
help: Add trailing comma
Safe fix
652 652 | }"""
653 653 |
654 654 | type X[
655 |- T
655 |+ T,
656 656 | ] = T
657 657 | def f[
658 658 | T
COM812 [*] Trailing comma missing
--> COM81.py:658:6
|
656 | ] = T
657 | def f[
658 | T
| ^
659 | ](): pass
660 | class C[
|
help: Add trailing comma
Safe fix
655 655 | T
656 656 | ] = T
657 657 | def f[
658 |- T
658 |+ T,
659 659 | ](): pass
660 660 | class C[
661 661 | T
COM812 [*] Trailing comma missing
--> COM81.py:661:6
|
659 | ](): pass
660 | class C[
661 | T
| ^
662 | ]: pass
|
help: Add trailing comma
Safe fix
658 658 | T
659 659 | ](): pass
660 660 | class C[
661 |- T
661 |+ T,
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
COM819 [*] Trailing comma prohibited
--> COM81.py:664:9
|
662 | ]: pass
663 |
664 | type X[T,] = T
| ^
665 | def f[T,](): pass
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
661 661 | T
662 662 | ]: pass
663 663 |
664 |-type X[T,] = T
664 |+type X[T] = T
665 665 | def f[T,](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:665:8
|
664 | type X[T,] = T
665 | def f[T,](): pass
| ^
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
665 |-def f[T,](): pass
665 |+def f[T](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:666:10
|
664 | type X[T,] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
| ^
|
help: Remove trailing comma
Safe fix
663 663 |
664 664 | type X[T,] = T
665 665 | def f[T,](): pass
666 |-class C[T,]: pass
666 |+class C[T]: pass

View File

@@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 0

View File

@@ -209,18 +209,18 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Expr::Call(ExprCall {
func: Box::new(Expr::Name(ExprName {
id: "dict.fromkeys".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
arguments: args,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}

View File

@@ -178,21 +178,21 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
let node1 = Expr::Name(ast::ExprName {
id: arg_name.into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let node2 = Expr::Attribute(ast::ExprAttribute {
value: Box::new(node1),
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
@@ -200,10 +200,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
args: Box::from([node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let call = node3;
@@ -223,7 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
})
.collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let bool_op = node;
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -92,14 +92,14 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) {
Expr::Tuple(ast::ExprTuple {
elts: unique_nodes.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: false,
})
}),
value: subscript.value.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
let fix = Fix::applicable_edit(

View File

@@ -187,7 +187,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
} else {
Some(right.clone())
@@ -202,7 +202,7 @@ fn generate_pep604_fix(
}
static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral {
node_index: AtomicNodeIndex::NONE,
node_index: AtomicNodeIndex::dummy(),
range: TextRange::new(TextSize::new(0), TextSize::new(0)),
});

View File

@@ -16,7 +16,7 @@ use crate::{checkers::ast::Checker, fix};
/// statement has no effect and should be omitted.
///
/// ## References
/// - [Typing Style Guide](https://typing.python.org/en/latest/guides/writing_stubs.html#language-features)
/// - [Static Typing with Python: Type Stubs](https://typing.python.org/en/latest/source/stubs.html)
#[derive(ViolationMetadata)]
pub(crate) struct FutureAnnotationsInStub;

View File

@@ -133,17 +133,17 @@ fn generate_union_fix(
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
let new_expr = Expr::Subscript(ExprSubscript {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
value: Box::new(Expr::Name(ExprName {
id: Name::new(binding),
ctx: ExprContext::Store,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(Expr::Tuple(ExprTuple {
elts: nodes.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: false,
})),

View File

@@ -205,13 +205,13 @@ fn create_fix(
let new_literal_expr = Expr::Subscript(ast::ExprSubscript {
value: Box::new(literal_subscript.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
slice: Box::new(if literal_elements.len() > 1 {
Expr::Tuple(ast::ExprTuple {
elts: literal_elements.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})
@@ -235,7 +235,7 @@ fn create_fix(
UnionKind::BitOr => {
let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
let content = checker.generator().expr(&union_expr);

View File

@@ -261,7 +261,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
} else {
Some(right.clone())

View File

@@ -140,12 +140,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts: literal_exprs.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
@@ -164,12 +164,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
}))
} else {

View File

@@ -134,12 +134,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: Name::new_static("type"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(pep_604_union(&elts)),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
if other_exprs.is_empty() {
@@ -159,7 +159,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: Name::new_static("type"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(Expr::Subscript(ast::ExprSubscript {
value: subscript.value.clone(),
@@ -171,22 +171,22 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: type_member,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
if other_exprs.is_empty() {
@@ -202,12 +202,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
elts: exprs.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
checker.generator().expr(&union)

View File

@@ -301,7 +301,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) -
})
.into_boxed_str(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags,
});
Some(generator.expr(&node))
@@ -367,14 +367,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
Expr::from(ast::StringLiteral {
value: Box::from(*name),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
@@ -404,14 +404,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
Expr::from(ast::StringLiteral {
value: Box::from(*name),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -440,7 +440,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
elts: elts.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -485,7 +485,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
elts: elts.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -166,7 +166,7 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt {
test: Box::new(expr.clone()),
msg: msg.map(|msg| Box::new(msg.clone())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}
@@ -176,7 +176,7 @@ fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr {
ops: Box::from([cmp_op]),
comparators: Box::from([right.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}
@@ -296,7 +296,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(expr.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}),
msg,
)
@@ -370,7 +370,7 @@ impl UnittestAssert {
};
let node = Expr::NoneLiteral(ast::ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let expr = compare(expr, cmp_op, &node);
Ok(assert(&expr, msg))
@@ -387,7 +387,7 @@ impl UnittestAssert {
id: Name::new_static("isinstance"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -395,10 +395,10 @@ impl UnittestAssert {
args: Box::from([(**obj).clone(), (**cls).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let isinstance = node1.into();
if matches!(self, UnittestAssert::IsInstance) {
@@ -408,7 +408,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(isinstance),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let expr = node.into();
Ok(assert(&expr, msg))
@@ -429,14 +429,14 @@ impl UnittestAssert {
id: Name::new_static("re"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprAttribute {
value: Box::new(node.into()),
attr: Identifier::new("search".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -444,10 +444,10 @@ impl UnittestAssert {
args: Box::from([(**regex).clone(), (**text).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let re_search = node2.into();
if matches!(self, UnittestAssert::Regex | UnittestAssert::RegexpMatches) {
@@ -457,7 +457,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(re_search),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Ok(assert(&node.into(), msg))
}

View File

@@ -60,7 +60,6 @@ mod tests {
}
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -421,7 +421,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
};
let isinstance_call = ast::ExprCall {
@@ -430,7 +430,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
id: Name::new_static("isinstance"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -438,10 +438,10 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
args: Box::from([target.clone(), tuple.into()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into();
@@ -458,7 +458,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
.chain(after)
.collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into();
let fixed_source = checker.generator().expr(&bool_op);
@@ -552,21 +552,21 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
elts: comparators.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
};
let node1 = ast::ExprName {
id: id.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCompare {
left: Box::new(node1.into()),
ops: Box::from([CmpOp::In]),
comparators: Box::from([node.into()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let in_expr = node2.into();
let mut diagnostic = checker.report_diagnostic(
@@ -589,7 +589,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
op: BoolOp::Or,
values: iter::once(in_expr).chain(unmatched).collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
};

View File

@@ -232,7 +232,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) {
}
}),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let new_env_var = node.into();
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -188,7 +188,7 @@ pub(crate) fn if_expr_with_true_false(
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -196,10 +196,10 @@ pub(crate) fn if_expr_with_true_false(
args: Box::from([test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -227,7 +227,7 @@ pub(crate) fn if_expr_with_false_true(
op: UnaryOp::Not,
operand: Box::new(test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -282,7 +282,7 @@ pub(crate) fn twisted_arms_in_ifexpr(
body: Box::new(node1),
orelse: Box::new(node),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node3.into()),

View File

@@ -186,7 +186,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp
ops: Box::from([CmpOp::NotEq]),
comparators: comparators.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -242,7 +242,7 @@ pub(crate) fn negation_with_not_equal_op(
ops: Box::from([CmpOp::Eq]),
comparators: comparators.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -284,7 +284,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -292,10 +292,10 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
args: Box::from([*operand.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&node1.into()),

View File

@@ -190,7 +190,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
attr: Identifier::new("get".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node3 = ast::ExprCall {
func: Box::new(node2.into()),
@@ -198,17 +198,17 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
args: Box::from([node1, node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node4 = expected_var.clone();
let node5 = ast::StmtAssign {
targets: vec![node4],
value: Box::new(node3.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let contents = checker.generator().stmt(&node5.into());
@@ -299,7 +299,7 @@ pub(crate) fn if_exp_instead_of_dict_get(
attr: Identifier::new("get".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let fixed_node = ast::ExprCall {
func: Box::new(dict_get_node.into()),
@@ -307,10 +307,10 @@ pub(crate) fn if_exp_instead_of_dict_get(
args: Box::from([dict_key_node, default_value_node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let contents = checker.generator().expr(&fixed_node.into());

View File

@@ -266,13 +266,13 @@ fn assignment_ternary(
body: Box::new(body_value.clone()),
orelse: Box::new(orelse_value.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node1.into()
}
@@ -282,13 +282,13 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
op: BoolOp::And,
values: vec![left_value.clone(), right_value.clone()],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node1.into()
}
@@ -296,12 +296,12 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
fn assignment_binary_or(target_var: &Expr, left_value: &Expr, right_value: &Expr) -> Stmt {
(ast::StmtAssign {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
targets: vec![target_var.clone()],
value: Box::new(
(ast::ExprBoolOp {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
op: BoolOp::Or,
values: vec![left_value.clone(), right_value.clone()],
})

View File

@@ -256,7 +256,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
left: left.clone(),
comparators: Box::new([right.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
}
@@ -264,7 +264,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
op: ast::UnaryOp::Not,
operand: Box::new(if_test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
}
} else if if_test.is_compare_expr() {
@@ -277,7 +277,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let call_node = ast::ExprCall {
func: Box::new(func_node.into()),
@@ -285,10 +285,10 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
args: Box::from([if_test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Some(Expr::Call(call_node))
} else {
@@ -301,7 +301,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
Stmt::Return(ast::StmtReturn {
value: Some(Box::new(expr.clone())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
});

View File

@@ -165,7 +165,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
ops: Box::from([op]),
comparators: Box::from([comparator.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
} else {
@@ -173,7 +173,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
op: UnaryOp::Not,
operand: Box::new(loop_.test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
}
@@ -182,7 +182,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
op: UnaryOp::Not,
operand: Box::new(loop_.test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
}
@@ -406,17 +406,17 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
ifs: vec![],
is_async: false,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: false,
};
let node1 = ast::ExprName {
id,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -424,15 +424,15 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
args: Box::from([node.into()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node3 = ast::StmtReturn {
value: Some(Box::new(node2.into())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
generator.stmt(&node3.into())
}

View File

@@ -9,8 +9,6 @@ use ruff_python_ast::{
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::preview::is_maxsplit_without_separator_fix_enabled;
use crate::settings::LinterSettings;
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
/// ## What it does
@@ -86,9 +84,7 @@ pub(crate) fn split_static_string(
let sep_arg = arguments.find_argument_value("sep", 0);
let split_replacement = if let Some(sep) = sep_arg {
match sep {
Expr::NoneLiteral(_) => {
split_default(str_value, maxsplit_value, direction, checker.settings())
}
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction),
Expr::StringLiteral(sep_value) => {
let sep_value_str = sep_value.value.to_str();
Some(split_sep(
@@ -104,7 +100,7 @@ pub(crate) fn split_static_string(
}
}
} else {
split_default(str_value, maxsplit_value, direction, checker.settings())
split_default(str_value, maxsplit_value, direction)
};
let mut diagnostic = checker.report_diagnostic(SplitStaticString, call.range());
@@ -163,14 +159,14 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::from(StringLiteral {
value: Box::from(*elt),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: element_flags,
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}
@@ -178,7 +174,6 @@ fn split_default(
str_value: &StringLiteralValue,
max_split: i32,
direction: Direction,
settings: &LinterSettings,
) -> Option<Expr> {
// From the Python documentation:
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
@@ -190,31 +185,10 @@ fn split_default(
let string_val = str_value.to_str();
match max_split.cmp(&0) {
Ordering::Greater => {
if !is_maxsplit_without_separator_fix_enabled(settings) {
return None;
}
let Ok(max_split) = usize::try_from(max_split) else {
return None;
};
let list_items: Vec<&str> = if direction == Direction::Left {
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
};
Some(construct_replacement(
&list_items,
str_value.first_literal_flags(),
))
// Autofix for `maxsplit` without separator not yet implemented, as
// `split_whitespace().remainder()` is not stable:
// https://doc.rust-lang.org/std/str/struct.SplitWhitespace.html#method.remainder
None
}
Ordering::Equal => {
// Behavior for maxsplit = 0 when sep is None:

View File

@@ -1439,7 +1439,6 @@ help: Replace with list literal
166 |+print(["S", "P", "L", "I", "T"])
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
169 169 |
SIM905 [*] Consider using a list literal instead of `str.split`
--> SIM905.py:167:7
@@ -1459,8 +1458,6 @@ help: Replace with list literal
167 |-print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
167 |+print([">"])
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
169 169 |
170 170 | # leading/trailing whitespace should not count towards maxsplit
SIM905 [*] Consider using a list literal instead of `str.split`
--> SIM905.py:168:7
@@ -1469,8 +1466,6 @@ SIM905 [*] Consider using a list literal instead of `str.split`
167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169 |
170 | # leading/trailing whitespace should not count towards maxsplit
|
help: Replace with list literal
@@ -1480,26 +1475,3 @@ help: Replace with list literal
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
168 |-print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
168 |+print(["<"])
169 169 |
170 170 | # leading/trailing whitespace should not count towards maxsplit
171 171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
SIM905 Consider using a list literal instead of `str.split`
--> SIM905.py:171:1
|
170 | # leading/trailing whitespace should not count towards maxsplit
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
help: Replace with list literal
SIM905 Consider using a list literal instead of `str.split`
--> SIM905.py:172:1
|
170 | # leading/trailing whitespace should not count towards maxsplit
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Replace with list literal

View File

@@ -101,7 +101,7 @@ fn fix_banned_relative_import(
names: names.clone(),
level: 0,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let content = generator.stmt(&node.into());
Some(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -436,7 +436,7 @@ impl<'a> QuoteAnnotator<'a> {
let annotation = subgenerator.expr(&expr_without_forward_references);
generator.expr(&Expr::from(ast::StringLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
value: annotation.into_boxed_str(),
flags: self.flags,
}))

View File

@@ -1,10 +1,9 @@
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::{Applicability, Edit, Fix, Violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
pub(crate) fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
arguments
@@ -184,17 +183,3 @@ pub(crate) fn check_os_pathlib_two_arg_calls(
});
}
}
pub(crate) fn has_unknown_keywords_or_starred_expr(
arguments: &ast::Arguments,
allowed: &[&str],
) -> bool {
if arguments.args.iter().any(Expr::is_starred_expr) {
return true;
}
arguments.keywords.iter().any(|kw| match &kw.arg {
Some(arg) => !allowed.contains(&arg.as_str()),
None => true,
})
}

View File

@@ -129,7 +129,6 @@ mod tests {
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
#[test_case(Rule::OsSymlink, Path::new("PTH211.py"))]
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -2,8 +2,6 @@ pub(crate) use glob_rule::*;
pub(crate) use invalid_pathlib_with_suffix::*;
pub(crate) use os_chmod::*;
pub(crate) use os_getcwd::*;
pub(crate) use os_makedirs::*;
pub(crate) use os_mkdir::*;
pub(crate) use os_path_abspath::*;
pub(crate) use os_path_basename::*;
pub(crate) use os_path_dirname::*;
@@ -24,7 +22,6 @@ pub(crate) use os_rename::*;
pub(crate) use os_replace::*;
pub(crate) use os_rmdir::*;
pub(crate) use os_sep_split::*;
pub(crate) use os_symlink::*;
pub(crate) use os_unlink::*;
pub(crate) use path_constructor_current_directory::*;
pub(crate) use replaceable_by_pathlib::*;
@@ -33,8 +30,6 @@ mod glob_rule;
mod invalid_pathlib_with_suffix;
mod os_chmod;
mod os_getcwd;
mod os_makedirs;
mod os_mkdir;
mod os_path_abspath;
mod os_path_basename;
mod os_path_dirname;
@@ -55,7 +50,6 @@ mod os_rename;
mod os_replace;
mod os_rmdir;
mod os_sep_split;
mod os_symlink;
mod os_unlink;
mod path_constructor_current_directory;
mod replaceable_by_pathlib;

View File

@@ -1,152 +0,0 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{ArgOrKeyword, ExprCall};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_makedirs_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.makedirs`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir(parents=True)` can improve readability over the
/// `os` module's counterparts (e.g., `os.makedirs()`.
///
/// ## Examples
/// ```python
/// import os
///
/// os.makedirs("./nested/directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./nested/directory/").mkdir(parents=True)
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMakedirs;
impl Violation for OsMakedirs {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).mkdir(parents=True)`".to_string())
}
}
/// PTH103
pub(crate) fn os_makedirs(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "makedirs"] {
return;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsMakedirs, call.func.range());
let Some(name) = call.arguments.find_argument_value("name", 0) else {
return;
};
if !is_fix_os_makedirs_enabled(checker.settings()) {
return;
}
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.makedirs)
// ```text
// 0 1 2
// os.makedirs(name, mode=0o777, exist_ok=False)
// ```
// We should not offer autofixes if there are more arguments
// than in the original signature
if call.arguments.len() > 3 {
return;
}
// We should not offer autofixes if there are keyword arguments
// that don't match the original function signature
if has_unknown_keywords_or_starred_expr(&call.arguments, &["name", "mode", "exist_ok"]) {
return;
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let locator = checker.locator();
let name_code = locator.slice(name.range());
let mode = call.arguments.find_argument("mode", 1);
let exist_ok = call.arguments.find_argument("exist_ok", 2);
let mkdir_args = match (mode, exist_ok) {
// Default to a keyword argument when alone.
(None, None) => "parents=True".to_string(),
// If either argument is missing, it's safe to add `parents` at the end.
(None, Some(arg)) | (Some(arg), None) => {
format!("{}, parents=True", locator.slice(arg))
}
// If they're all positional, `parents` has to be positional too.
(Some(ArgOrKeyword::Arg(mode)), Some(ArgOrKeyword::Arg(exist_ok))) => {
format!("{}, True, {}", locator.slice(mode), locator.slice(exist_ok))
}
// If either argument is a keyword, we can put `parents` at the end again.
(Some(mode), Some(exist_ok)) => format!(
"{}, {}, parents=True",
locator.slice(mode),
locator.slice(exist_ok)
),
};
let replacement = if is_pathlib_path_call(checker, name) {
format!("{name_code}.mkdir({mkdir_args})")
} else {
format!("{binding}({name_code}).mkdir({mkdir_args})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@@ -1,136 +0,0 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_mkdir_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_keyword_only_argument_non_default,
is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.mkdir`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir()` can improve readability over the `os`
/// module's counterparts (e.g., `os.mkdir()`).
///
/// ## Examples
/// ```python
/// import os
///
/// os.mkdir("./directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./directory/").mkdir()
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMkdir;
impl Violation for OsMkdir {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.mkdir()` should be replaced by `Path.mkdir()`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).mkdir()`".to_string())
}
}
/// PTH102
pub(crate) fn os_mkdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "mkdir"] {
return;
}
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
// ```text
// 0 1 2
// os.mkdir(path, mode=0o777, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsMkdir, call.func.range());
let Some(path) = call.arguments.find_argument_value("path", 0) else {
return;
};
if !is_fix_os_mkdir_enabled(checker.settings()) {
return;
}
if call.arguments.len() > 2 {
return;
}
if has_unknown_keywords_or_starred_expr(&call.arguments, &["path", "mode"]) {
return;
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let path_code = checker.locator().slice(path.range());
let mkdir_args = call
.arguments
.find_argument_value("mode", 1)
.map(|expr| format!("mode={}", checker.locator().slice(expr.range())))
.unwrap_or_default();
let replacement = if is_pathlib_path_call(checker, path) {
format!("{path_code}.mkdir({mkdir_args})")
} else {
format!("{binding}({path_code}).mkdir({mkdir_args})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@@ -1,153 +0,0 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_symlink_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_keyword_only_argument_non_default,
is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.symlink`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os.symlink`.
///
/// ## Example
/// ```python
/// import os
///
/// os.symlink("usr/bin/python", "tmp/python", target_is_directory=False)
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("tmp/python").symlink_to("usr/bin/python")
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsSymlink;
impl Violation for OsSymlink {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.symlink` should be replaced by `Path.symlink_to`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).symlink_to(...)`".to_string())
}
}
/// PTH211
pub(crate) fn os_symlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "symlink"] {
return;
}
// `dir_fd` is not supported by pathlib, so check if there are non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.symlink)
// ```text
// 0 1 2 3
// os.symlink(src, dst, target_is_directory=False, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsSymlink, call.func.range());
if !is_fix_os_symlink_enabled(checker.settings()) {
return;
}
if call.arguments.len() > 3 {
return;
}
if has_unknown_keywords_or_starred_expr(
&call.arguments,
&["src", "dst", "target_is_directory", "dir_fd"],
) {
return;
}
let (Some(src), Some(dst)) = (
call.arguments.find_argument_value("src", 0),
call.arguments.find_argument_value("dst", 1),
) else {
return;
};
let target_is_directory_arg = call.arguments.find_argument_value("target_is_directory", 2);
if let Some(expr) = &target_is_directory_arg {
if expr.as_boolean_literal_expr().is_none() {
return;
}
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let locator = checker.locator();
let src_code = locator.slice(src.range());
let dst_code = locator.slice(dst.range());
let target_is_directory = target_is_directory_arg
.and_then(|expr| {
let code = locator.slice(expr.range());
expr.as_boolean_literal_expr()
.is_none_or(|bl| bl.value)
.then_some(format!(", target_is_directory={code}"))
})
.unwrap_or_default();
let replacement = if is_pathlib_path_call(checker, dst) {
format!("{dst_code}.symlink_to({src_code}{target_is_directory})")
} else {
format!("{binding}({dst_code}).symlink_to({src_code}{target_is_directory})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@@ -7,7 +7,10 @@ use crate::rules::flake8_use_pathlib::helpers::{
};
use crate::rules::flake8_use_pathlib::{
rules::Glob,
violations::{BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, PyPath},
violations::{
BuiltinOpen, Joiner, OsListdir, OsMakedirs, OsMkdir, OsPathJoin, OsPathSplitext, OsStat,
OsSymlink, PyPath,
},
};
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
@@ -17,6 +20,21 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
let range = call.func.range();
match qualified_name.segments() {
// PTH102
["os", "makedirs"] => checker.report_diagnostic_if_enabled(OsMakedirs, range),
// PTH103
["os", "mkdir"] => {
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
// ```text
// 0 1 2
// os.mkdir(path, mode=0o777, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
checker.report_diagnostic_if_enabled(OsMkdir, range)
}
// PTH116
["os", "stat"] => {
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
@@ -60,6 +78,20 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
),
// PTH122
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range),
// PTH211
["os", "symlink"] => {
// `dir_fd` is not supported by pathlib, so check if there are non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.symlink)
// ```text
// 0 1 2 3
// os.symlink(src, dst, target_is_directory=False, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
checker.report_diagnostic_if_enabled(OsSymlink, range)
}
// PTH123
["" | "builtins", "open"] => {
// `closefd` and `opener` are not supported by pathlib, so check if they

View File

@@ -9,7 +9,6 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:6:1
@@ -19,7 +18,6 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
| ^^^^^^^^^^
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:9:1
@@ -31,7 +29,6 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:10:1
@@ -41,58 +38,3 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
| ^^^^^^^^^^
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:17:1
|
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
| ^^^^^^^^^^
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:18:1
|
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
| ^^^^^^^^^^
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:20:1
|
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
| ^^^^^^^^^^
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:22:1
|
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
| ^^^^^^^^^^
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:23:1
|
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
| ^^^^^^^^^^
|
help: Replace with `Path(...).symlink_to(...)`

View File

@@ -34,7 +34,6 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | os.makedirs(p)
11 | os.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:10:1
@@ -46,7 +45,6 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | os.rename(p)
12 | os.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> full_name.py:11:1
@@ -421,77 +419,5 @@ PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
108 | os.getcwd()
109 | os.getcwdb()
| ^^^^^^^^^^
110 |
111 | os.mkdir(path="directory")
|
help: Replace with `Path.cwd()`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:111:1
|
109 | os.getcwdb()
110 |
111 | os.mkdir(path="directory")
| ^^^^^^^^
112 |
113 | os.mkdir(
|
help: Replace with `Path(...).mkdir()`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:113:1
|
111 | os.mkdir(path="directory")
112 |
113 | os.mkdir(
| ^^^^^^^^
114 | # comment 1
115 | "directory",
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:121:1
|
119 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 |
121 | os.makedirs("name", 0o777, exist_ok=False)
| ^^^^^^^^^^^
122 |
123 | os.makedirs("name", 0o777, False)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:123:1
|
121 | os.makedirs("name", 0o777, exist_ok=False)
122 |
123 | os.makedirs("name", 0o777, False)
| ^^^^^^^^^^^
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:125:1
|
123 | os.makedirs("name", 0o777, False)
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
| ^^^^^^^^^^^
126 |
127 | os.makedirs("name", unknown_kwarg=True)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:127:1
|
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |
127 | os.makedirs("name", unknown_kwarg=True)
| ^^^^^^^^^^^
|
help: Replace with `Path(...).mkdir(parents=True)`

View File

@@ -34,7 +34,6 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | foo.makedirs(p)
11 | foo.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_as.py:10:1
@@ -46,7 +45,6 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | foo.rename(p)
12 | foo.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_as.py:11:1

View File

@@ -34,7 +34,6 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
12 | makedirs(p)
13 | rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from.py:12:1
@@ -46,7 +45,6 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
13 | rename(p)
14 | replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from.py:13:1

View File

@@ -34,7 +34,6 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
17 | xmakedirs(p)
18 | xrename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from_as.py:17:1
@@ -46,7 +45,6 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
18 | xrename(p)
19 | xreplace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from_as.py:18:1

View File

@@ -1,166 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:5:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
| ^^^^^^^^^^
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
2 2 | from pathlib import Path
3 3 |
4 4 |
5 |-os.symlink("usr/bin/python", "tmp/python")
5 |+Path("tmp/python").symlink_to("usr/bin/python")
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:6:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
6 | os.symlink(b"usr/bin/python", b"tmp/python")
| ^^^^^^^^^^
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
3 3 |
4 4 |
5 5 | os.symlink("usr/bin/python", "tmp/python")
6 |-os.symlink(b"usr/bin/python", b"tmp/python")
6 |+Path(b"tmp/python").symlink_to(b"usr/bin/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:9:1
|
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
| ^^^^^^^^^^
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 |-os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
9 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
10 10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 12 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:10:1
|
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
| ^^^^^^^^^^
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
10 |-os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
10 |+Path(b"tmp/python").symlink_to(b"usr/bin/python", target_is_directory=True)
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 12 |
13 13 | fd = os.open(".", os.O_RDONLY)
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:17:1
|
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
| ^^^^^^^^^^
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:18:1
|
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
| ^^^^^^^^^^
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
15 15 | os.close(fd)
16 16 |
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
18 |+Path("tmp/python").symlink_to("usr/bin/python")
19 19 |
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 21 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:20:1
|
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
| ^^^^^^^^^^
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 19 |
20 |-os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
20 |+Path("tmp/python").symlink_to("usr/bin/python")
21 21 |
22 22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:22:1
|
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
| ^^^^^^^^^^
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
19 19 |
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 21 |
22 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
22 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:23:1
|
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
| ^^^^^^^^^^
|
help: Replace with `Path(...).symlink_to(...)`

View File

@@ -38,7 +38,7 @@ PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
help: Replace with `Path(...).chmod(...)`
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:9:7
|
7 | a = os.path.abspath(p)
@@ -48,25 +48,8 @@ PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
10 | os.makedirs(p)
11 | os.rename(p)
|
help: Replace with `Path(...).mkdir()`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
6 7 |
7 8 | a = os.path.abspath(p)
8 9 | aa = os.chmod(p)
9 |-aaa = os.mkdir(p)
10 |+aaa = pathlib.Path(p).mkdir()
10 11 | os.makedirs(p)
11 12 | os.rename(p)
12 13 | os.replace(p)
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:10:1
|
8 | aa = os.chmod(p)
@@ -76,24 +59,6 @@ PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | os.rename(p)
12 | os.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
7 8 | a = os.path.abspath(p)
8 9 | aa = os.chmod(p)
9 10 | aaa = os.mkdir(p)
10 |-os.makedirs(p)
11 |+pathlib.Path(p).mkdir(parents=True)
11 12 | os.rename(p)
12 13 | os.replace(p)
13 14 | os.rmdir(p)
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> full_name.py:11:1
@@ -680,8 +645,6 @@ help: Replace with `Path.cwd()`
108 |-os.getcwd()
109 |+pathlib.Path.cwd()
109 110 | os.getcwdb()
110 111 |
111 112 | os.mkdir(path="directory")
PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
--> full_name.py:109:1
@@ -689,8 +652,6 @@ PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
108 | os.getcwd()
109 | os.getcwdb()
| ^^^^^^^^^^
110 |
111 | os.mkdir(path="directory")
|
help: Replace with `Path.cwd()`
@@ -707,164 +668,3 @@ help: Replace with `Path.cwd()`
108 109 | os.getcwd()
109 |-os.getcwdb()
110 |+pathlib.Path.cwd()
110 111 |
111 112 | os.mkdir(path="directory")
112 113 |
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:111:1
|
109 | os.getcwdb()
110 |
111 | os.mkdir(path="directory")
| ^^^^^^^^
112 |
113 | os.mkdir(
|
help: Replace with `Path(...).mkdir()`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
108 109 | os.getcwd()
109 110 | os.getcwdb()
110 111 |
111 |-os.mkdir(path="directory")
112 |+pathlib.Path("directory").mkdir()
112 113 |
113 114 | os.mkdir(
114 115 | # comment 1
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:113:1
|
111 | os.mkdir(path="directory")
112 |
113 | os.mkdir(
| ^^^^^^^^
114 | # comment 1
115 | "directory",
|
help: Replace with `Path(...).mkdir()`
Unsafe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
110 111 |
111 112 | os.mkdir(path="directory")
112 113 |
113 |-os.mkdir(
114 |- # comment 1
115 |- "directory",
116 |- mode=0o777
117 |-)
114 |+pathlib.Path("directory").mkdir(mode=0o777)
118 115 |
119 116 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 117 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:121:1
|
119 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 |
121 | os.makedirs("name", 0o777, exist_ok=False)
| ^^^^^^^^^^^
122 |
123 | os.makedirs("name", 0o777, False)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
118 119 |
119 120 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 121 |
121 |-os.makedirs("name", 0o777, exist_ok=False)
122 |+pathlib.Path("name").mkdir(0o777, exist_ok=False, parents=True)
122 123 |
123 124 | os.makedirs("name", 0o777, False)
124 125 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:123:1
|
121 | os.makedirs("name", 0o777, exist_ok=False)
122 |
123 | os.makedirs("name", 0o777, False)
| ^^^^^^^^^^^
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
120 121 |
121 122 | os.makedirs("name", 0o777, exist_ok=False)
122 123 |
123 |-os.makedirs("name", 0o777, False)
124 |+pathlib.Path("name").mkdir(0o777, True, False)
124 125 |
125 126 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 127 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:125:1
|
123 | os.makedirs("name", 0o777, False)
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
| ^^^^^^^^^^^
126 |
127 | os.makedirs("name", unknown_kwarg=True)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
122 123 |
123 124 | os.makedirs("name", 0o777, False)
124 125 |
125 |-os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |+pathlib.Path("name").mkdir(mode=0o777, exist_ok=False, parents=True)
126 127 |
127 128 | os.makedirs("name", unknown_kwarg=True)
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:127:1
|
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |
127 | os.makedirs("name", unknown_kwarg=True)
| ^^^^^^^^^^^
|
help: Replace with `Path(...).mkdir(parents=True)`

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