Compare commits
73 Commits
dcreager/h
...
david/fiel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea56e2415f | ||
|
|
23543194fc | ||
|
|
4dc88a0a5f | ||
|
|
8817ea5c84 | ||
|
|
85ff4f3eef | ||
|
|
c6959381f8 | ||
|
|
270ba71ad5 | ||
|
|
cb4d4493d7 | ||
|
|
cafb96aa7a | ||
|
|
651f7963a7 | ||
|
|
4fc7dd300c | ||
|
|
a93618ed23 | ||
|
|
9e1aafd0ce | ||
|
|
abf685b030 | ||
|
|
e1e3eb7209 | ||
|
|
43eddc566f | ||
|
|
f8e00e3cd9 | ||
|
|
591e9bbccb | ||
|
|
1ed9b215b9 | ||
|
|
9090aead0f | ||
|
|
441ba20876 | ||
|
|
6341bb7403 | ||
|
|
ac2c530377 | ||
|
|
c69fa75cd5 | ||
|
|
f73bb45be6 | ||
|
|
e338d2095e | ||
|
|
5e08e5451d | ||
|
|
aba0bd568e | ||
|
|
83b497ce88 | ||
|
|
2b729b4d52 | ||
|
|
4b8e278a88 | ||
|
|
d912f13661 | ||
|
|
71f8389f61 | ||
|
|
373fe8a39c | ||
|
|
975891fc90 | ||
|
|
195e8f0684 | ||
|
|
513d2996ec | ||
|
|
d83d7a0dcd | ||
|
|
565dbf3c9d | ||
|
|
f715d70be1 | ||
|
|
9b9c9ae092 | ||
|
|
c80ee1a50b | ||
|
|
350042b801 | ||
|
|
e02cdd350e | ||
|
|
e3b910c41a | ||
|
|
0e8c02aea6 | ||
|
|
74b2c4c2e4 | ||
|
|
89b67a2448 | ||
|
|
6be344af65 | ||
|
|
89f9dd6b43 | ||
|
|
1935896e6b | ||
|
|
7064c38e53 | ||
|
|
dc64c08633 | ||
|
|
11a9e7ee44 | ||
|
|
bbd3856de8 | ||
|
|
ae83a1fd2d | ||
|
|
44807c4a05 | ||
|
|
69f9182033 | ||
|
|
949a4f1c42 | ||
|
|
a82833a998 | ||
|
|
4bd454f9b5 | ||
|
|
66885e4bce | ||
|
|
8248193ed9 | ||
|
|
b086ffe921 | ||
|
|
537ec5f012 | ||
|
|
db91ac7dce | ||
|
|
75f3c0e8e6 | ||
|
|
f0d0b57900 | ||
|
|
b0c6217e0b | ||
|
|
f054b8a55e | ||
|
|
b9c84add07 | ||
|
|
150ea92d03 | ||
|
|
697998f836 |
72
.github/workflows/ci.yaml
vendored
72
.github/workflows/ci.yaml
vendored
@@ -142,7 +142,7 @@ jobs:
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py_fuzzer/**' \
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py-fuzzer/**' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -361,6 +361,37 @@ jobs:
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
|
||||
cargo-test-macos:
|
||||
name: "cargo test (macos)"
|
||||
runs-on: macos-latest
|
||||
needs: determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
|
||||
cargo-test-wasm:
|
||||
name: "cargo test (wasm)"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -391,23 +422,6 @@ jobs:
|
||||
cd crates/ty_wasm
|
||||
wasm-pack test --node
|
||||
|
||||
cargo-build-release:
|
||||
name: "cargo build (release)"
|
||||
runs-on: macos-latest
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
cargo-build-msrv:
|
||||
name: "cargo build (msrv)"
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
@@ -452,7 +466,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
|
||||
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
||||
- name: "Install cargo-fuzz"
|
||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||
@@ -703,7 +717,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
|
||||
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -721,7 +735,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run ty completion evaluation"
|
||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.1 --tasks /tmp/completion-evaluation-tasks.csv
|
||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
|
||||
- name: "Ensure there are no changes"
|
||||
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
|
||||
|
||||
@@ -953,7 +967,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
@@ -988,19 +1002,23 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
benchmarks-walltime:
|
||||
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
||||
runs-on: codspeed-macro
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TY_LOG: ruff_benchmark=debug
|
||||
strategy:
|
||||
matrix:
|
||||
benchmarks:
|
||||
- "medium|multithreaded"
|
||||
- "small|large"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -1022,7 +1040,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
env:
|
||||
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
||||
# appear to provide much useful insight for our walltime benchmarks right now
|
||||
@@ -1030,5 +1048,5 @@ jobs:
|
||||
CODSPEED_PERF_ENABLED: false
|
||||
with:
|
||||
mode: walltime
|
||||
run: cargo codspeed run
|
||||
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -64,12 +64,12 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@279f8a15b0e7f77213bf9096dbc2335a19ef89c5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
diff \
|
||||
--profile=release \
|
||||
--profile=profiling \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
|
||||
4
.github/workflows/ty-ecosystem-report.yaml
vendored
4
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -49,13 +49,13 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@279f8a15b0e7f77213bf9096dbc2335a19ef89c5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--profile=release \
|
||||
--profile=profiling \
|
||||
--projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
|
||||
--output ecosystem-diagnostics.json
|
||||
|
||||
|
||||
147
Cargo.lock
generated
147
Cargo.lock
generated
@@ -50,9 +50,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -65,9 +65,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-lossy"
|
||||
@@ -214,15 +214,6 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
@@ -243,6 +234,26 @@ dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -316,9 +327,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
|
||||
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -350,6 +361,15 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
@@ -400,6 +420,17 @@ dependencies = [
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
@@ -486,16 +517,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117"
|
||||
checksum = "d0f62ea8934802f8b374bf691eea524c3aa444d7014f604dd4182a3667b69510"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 1.3.3",
|
||||
"bindgen",
|
||||
"cc",
|
||||
"colored 2.2.0",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"nix 0.30.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"statrs",
|
||||
@@ -504,20 +536,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78f6c1c6bed5fd84d319e8b0889da051daa361c79b7709c9394dfe1a882bba67"
|
||||
checksum = "d87efbc015fc0ff1b2001cd87df01c442824de677e01a77230bf091534687abb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
"colored 2.2.0",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c989289ce6b1cbde72ed560496cb8fbf5aa14d5ef5666f168e7f87751038352e"
|
||||
checksum = "ae5713ace440123bb4f1f78dd068d46872cb8548bfe61f752e7b2ad2c06d7f00"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
@@ -540,20 +574,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6"
|
||||
checksum = "95b4214b974f8f5206497153e89db90274e623f06b00bf4b9143eeb7735d975d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codspeed",
|
||||
"codspeed-divan-compat-macros",
|
||||
"codspeed-divan-compat-walltime",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8"
|
||||
checksum = "a53f34a16cb70ce4fd9ad57e1db016f0718e434f34179ca652006443b9a39967"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools 0.14.0",
|
||||
@@ -565,9 +601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "3.0.5"
|
||||
version = "4.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f"
|
||||
checksum = "e8a5099050c8948dce488b8eaa2e68dc5cf571cb8f9fce99aaaecbdddb940bcd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -1527,7 +1563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.15.5",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1801,15 +1837,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.8.4"
|
||||
version = "1.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
|
||||
checksum = "9d56bcd52d9b5e5f43e7fba20eb1f423ccb18c84cdf1cb506b8c1b95776b0b49"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"libcst_derive",
|
||||
@@ -1822,14 +1858,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.8.4"
|
||||
version = "1.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
|
||||
checksum = "3fcf5a725c4db703660124fe0edb98285f1605d0b87b7ee8684b699764a4f01a"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.44"
|
||||
@@ -1971,9 +2017,9 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -2482,6 +2528,16 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
@@ -2513,9 +2569,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.13.6"
|
||||
version = "0.13.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec768e063102b426e8962989758115e8659485124de9207bc365fab524125d65"
|
||||
checksum = "f6d755483ad14b49e76713b52285235461a5b4f73f17612353e11a5de36a5fd2"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
@@ -2713,9 +2769,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2725,9 +2781,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2764,7 +2820,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_fs",
|
||||
"bincode 2.0.1",
|
||||
"bincode",
|
||||
"bitflags 2.9.4",
|
||||
"cachedir",
|
||||
"clap",
|
||||
@@ -4443,6 +4499,7 @@ dependencies = [
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"path-slash",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
|
||||
34
Cargo.toml
34
Cargo.toml
@@ -71,8 +71,8 @@ clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.6.0" }
|
||||
clearscreen = { version = "4.0.0" }
|
||||
csv = { version = "1.3.1" }
|
||||
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
|
||||
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
|
||||
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
@@ -268,12 +268,7 @@ 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
|
||||
# and runtime performance[1].
|
||||
#
|
||||
# [1]: https://github.com/astral-sh/ruff/pull/9031
|
||||
lto = "thin"
|
||||
lto = "fat"
|
||||
codegen-units = 16
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
@@ -283,6 +278,8 @@ codegen-units = 16
|
||||
codegen-units = 1
|
||||
[profile.release.package.ruff_python_ast]
|
||||
codegen-units = 1
|
||||
[profile.release.package.salsa]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
@@ -298,11 +295,30 @@ opt-level = 3
|
||||
[profile.dev.package.ruff_python_parser]
|
||||
opt-level = 1
|
||||
|
||||
# This profile is meant to mimic the `release` profile as closely as
|
||||
# possible, but using settings that are more beneficial for iterative
|
||||
# development. That is, the `release` profile is intended for actually
|
||||
# building the release, where as `profiling` is meant for building ty/ruff
|
||||
# for running benchmarks.
|
||||
#
|
||||
# The main differences here are to avoid stripping debug information
|
||||
# and disabling fat lto. This does result in a mismatch between our release
|
||||
# configuration and our benchmarking configuration, which is unfortunate.
|
||||
# But compile times with `lto = fat` are completely untenable.
|
||||
#
|
||||
# This setup does risk that we are measuring something in benchmarks
|
||||
# that we aren't shipping, but in order to make those two the same, we'd
|
||||
# either need to make compile times way worse for development, or take
|
||||
# a hit to binary size and a slight hit to runtime performance in our
|
||||
# release builds.
|
||||
#
|
||||
# Use the `--profile profiling` flag to show symbols in release mode.
|
||||
# e.g. `cargo build --profile profiling`
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = 1
|
||||
strip = false
|
||||
debug = "full"
|
||||
lto = false
|
||||
|
||||
# The profile that 'cargo dist' will build with.
|
||||
[profile.dist]
|
||||
|
||||
@@ -28,7 +28,7 @@ An extremely fast Python linter and code formatter, written in Rust.
|
||||
- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black)
|
||||
- 🐍 Installable via `pip`
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 🤝 Python 3.13 compatibility
|
||||
- 🤝 Python 3.14 compatibility
|
||||
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruffs-linter-compare-to-flake8), isort, and [Black](https://docs.astral.sh/ruff/faq/#how-does-ruffs-formatter-compare-to-black)
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
|
||||
@@ -599,7 +599,7 @@ impl<'a> ProjectBenchmark<'a> {
|
||||
self.project
|
||||
.check_paths()
|
||||
.iter()
|
||||
.map(|path| path.to_path_buf())
|
||||
.map(|path| SystemPathBuf::from(*path))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
@@ -645,8 +645,8 @@ fn hydra(criterion: &mut Criterion) {
|
||||
name: "hydra-zen",
|
||||
repository: "https://github.com/mit-ll-responsible-ai/hydra-zen",
|
||||
commit: "dd2b50a9614c6f8c46c5866f283c8f7e7a960aa8",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec!["pydantic", "beartype", "hydra-core"],
|
||||
paths: &["src"],
|
||||
dependencies: &["pydantic", "beartype", "hydra-core"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -662,8 +662,8 @@ fn attrs(criterion: &mut Criterion) {
|
||||
name: "attrs",
|
||||
repository: "https://github.com/python-attrs/attrs",
|
||||
commit: "a6ae894aad9bc09edc7cdad8c416898784ceec9b",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -679,8 +679,8 @@ fn anyio(criterion: &mut Criterion) {
|
||||
name: "anyio",
|
||||
repository: "https://github.com/agronholm/anyio",
|
||||
commit: "561d81270a12f7c6bbafb5bc5fad99a2a13f96be",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -696,8 +696,8 @@ fn datetype(criterion: &mut Criterion) {
|
||||
name: "DateType",
|
||||
repository: "https://github.com/glyph/DateType",
|
||||
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
max_dep_date: "2025-07-04",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use divan::{Bencher, bench};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
|
||||
@@ -13,29 +12,39 @@ use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
|
||||
struct Benchmark<'a> {
|
||||
project: InstalledProject<'a>,
|
||||
project: RealWorldProject<'a>,
|
||||
installed_project: std::sync::OnceLock<InstalledProject<'a>>,
|
||||
max_diagnostics: usize,
|
||||
}
|
||||
|
||||
impl<'a> Benchmark<'a> {
|
||||
fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
|
||||
let setup_project = project.setup().expect("Failed to setup project");
|
||||
|
||||
const fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
|
||||
Self {
|
||||
project: setup_project,
|
||||
project,
|
||||
installed_project: std::sync::OnceLock::new(),
|
||||
max_diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
fn installed_project(&self) -> &InstalledProject<'a> {
|
||||
self.installed_project.get_or_init(|| {
|
||||
self.project
|
||||
.clone()
|
||||
.setup()
|
||||
.expect("Failed to setup project")
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_iteration(&self) -> ProjectDatabase {
|
||||
let root = SystemPathBuf::from_path_buf(self.project.path.clone()).unwrap();
|
||||
let installed_project = self.installed_project();
|
||||
let root = SystemPathBuf::from_path_buf(installed_project.path.clone()).unwrap();
|
||||
let system = OsSystem::new(&root);
|
||||
|
||||
let mut metadata = ProjectMetadata::discover(&root, &system).unwrap();
|
||||
|
||||
metadata.apply_options(Options {
|
||||
environment: Some(EnvironmentOptions {
|
||||
python_version: Some(RangedValue::cli(self.project.config.python_version)),
|
||||
python_version: Some(RangedValue::cli(installed_project.config.python_version)),
|
||||
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
..EnvironmentOptions::default()
|
||||
}),
|
||||
@@ -46,7 +55,7 @@ impl<'a> Benchmark<'a> {
|
||||
|
||||
db.project().set_included_paths(
|
||||
&mut db,
|
||||
self.project
|
||||
installed_project
|
||||
.check_paths()
|
||||
.iter()
|
||||
.map(|path| SystemPath::absolute(path, &root))
|
||||
@@ -58,7 +67,7 @@ impl<'a> Benchmark<'a> {
|
||||
|
||||
impl Display for Benchmark<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.project.config.name)
|
||||
self.project.name.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,166 +84,150 @@ fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
|
||||
);
|
||||
}
|
||||
|
||||
static ALTAIR: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "altair",
|
||||
repository: "https://github.com/vega/altair",
|
||||
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
|
||||
paths: vec![SystemPath::new("altair")],
|
||||
dependencies: vec![
|
||||
"jinja2",
|
||||
"narwhals",
|
||||
"numpy",
|
||||
"packaging",
|
||||
"pandas-stubs",
|
||||
"pyarrow-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
"types-jsonschema",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
1000,
|
||||
)
|
||||
});
|
||||
static ALTAIR: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "altair",
|
||||
repository: "https://github.com/vega/altair",
|
||||
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
|
||||
paths: &["altair"],
|
||||
dependencies: &[
|
||||
"jinja2",
|
||||
"narwhals",
|
||||
"numpy",
|
||||
"packaging",
|
||||
"pandas-stubs",
|
||||
"pyarrow-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
"types-jsonschema",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
1000,
|
||||
);
|
||||
|
||||
static COLOUR_SCIENCE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "colour-science",
|
||||
repository: "https://github.com/colour-science/colour",
|
||||
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
|
||||
paths: vec![SystemPath::new("colour")],
|
||||
dependencies: vec![
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
600,
|
||||
)
|
||||
});
|
||||
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "colour-science",
|
||||
repository: "https://github.com/colour-science/colour",
|
||||
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
|
||||
paths: &["colour"],
|
||||
dependencies: &[
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
600,
|
||||
);
|
||||
|
||||
static FREQTRADE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "freqtrade",
|
||||
repository: "https://github.com/freqtrade/freqtrade",
|
||||
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
|
||||
paths: vec![SystemPath::new("freqtrade")],
|
||||
dependencies: vec![
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pydantic",
|
||||
"sqlalchemy",
|
||||
"types-cachetools",
|
||||
"types-filelock",
|
||||
"types-python-dateutil",
|
||||
"types-requests",
|
||||
"types-tabulate",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
400,
|
||||
)
|
||||
});
|
||||
static FREQTRADE: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "freqtrade",
|
||||
repository: "https://github.com/freqtrade/freqtrade",
|
||||
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
|
||||
paths: &["freqtrade"],
|
||||
dependencies: &[
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pydantic",
|
||||
"sqlalchemy",
|
||||
"types-cachetools",
|
||||
"types-filelock",
|
||||
"types-python-dateutil",
|
||||
"types-requests",
|
||||
"types-tabulate",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
400,
|
||||
);
|
||||
|
||||
static PANDAS: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pandas",
|
||||
repository: "https://github.com/pandas-dev/pandas",
|
||||
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
|
||||
paths: vec![SystemPath::new("pandas")],
|
||||
dependencies: vec![
|
||||
"numpy",
|
||||
"types-python-dateutil",
|
||||
"types-pytz",
|
||||
"types-PyMySQL",
|
||||
"types-setuptools",
|
||||
"pytest",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
3000,
|
||||
)
|
||||
});
|
||||
static PANDAS: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pandas",
|
||||
repository: "https://github.com/pandas-dev/pandas",
|
||||
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
|
||||
paths: &["pandas"],
|
||||
dependencies: &[
|
||||
"numpy",
|
||||
"types-python-dateutil",
|
||||
"types-pytz",
|
||||
"types-PyMySQL",
|
||||
"types-setuptools",
|
||||
"pytest",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
3000,
|
||||
);
|
||||
|
||||
static PYDANTIC: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pydantic",
|
||||
repository: "https://github.com/pydantic/pydantic",
|
||||
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
|
||||
paths: vec![SystemPath::new("pydantic")],
|
||||
dependencies: vec![
|
||||
"annotated-types",
|
||||
"pydantic-core",
|
||||
"typing-extensions",
|
||||
"typing-inspection",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
1000,
|
||||
)
|
||||
});
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pydantic",
|
||||
repository: "https://github.com/pydantic/pydantic",
|
||||
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
|
||||
paths: &["pydantic"],
|
||||
dependencies: &[
|
||||
"annotated-types",
|
||||
"pydantic-core",
|
||||
"typing-extensions",
|
||||
"typing-inspection",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
1000,
|
||||
);
|
||||
|
||||
static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "sympy",
|
||||
repository: "https://github.com/sympy/sympy",
|
||||
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
|
||||
paths: vec![SystemPath::new("sympy")],
|
||||
dependencies: vec!["mpmath"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13000,
|
||||
)
|
||||
});
|
||||
static SYMPY: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "sympy",
|
||||
repository: "https://github.com/sympy/sympy",
|
||||
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
|
||||
paths: &["sympy"],
|
||||
dependencies: &["mpmath"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13000,
|
||||
);
|
||||
|
||||
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "tanjun",
|
||||
repository: "https://github.com/FasterSpeeding/Tanjun",
|
||||
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
|
||||
paths: vec![SystemPath::new("tanjun")],
|
||||
dependencies: vec!["hikari", "alluka"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
100,
|
||||
)
|
||||
});
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "tanjun",
|
||||
repository: "https://github.com/FasterSpeeding/Tanjun",
|
||||
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
|
||||
paths: &["tanjun"],
|
||||
dependencies: &["hikari", "alluka"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
100,
|
||||
);
|
||||
|
||||
static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "static-frame",
|
||||
repository: "https://github.com/static-frame/static-frame",
|
||||
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
|
||||
paths: vec![SystemPath::new("static_frame")],
|
||||
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
|
||||
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
|
||||
// (seems to be built from source on the Codspeed CI runners for some reason).
|
||||
dependencies: vec!["numpy"],
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
600,
|
||||
)
|
||||
});
|
||||
static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "static-frame",
|
||||
repository: "https://github.com/static-frame/static-frame",
|
||||
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
|
||||
paths: &["static_frame"],
|
||||
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
|
||||
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
|
||||
// (seems to be built from source on the Codspeed CI runners for some reason).
|
||||
dependencies: &["numpy"],
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
630,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
@@ -245,22 +238,22 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
});
|
||||
}
|
||||
|
||||
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
|
||||
#[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
|
||||
fn small(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS, &*STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
fn medium(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
|
||||
#[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
|
||||
fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
|
||||
#[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ pub struct RealWorldProject<'a> {
|
||||
/// Specific commit hash to checkout
|
||||
pub commit: &'a str,
|
||||
/// List of paths within the project to check (`ty check <paths>`)
|
||||
pub paths: Vec<&'a SystemPath>,
|
||||
pub paths: &'a [&'a str],
|
||||
/// Dependencies to install via uv
|
||||
pub dependencies: Vec<&'a str>,
|
||||
pub dependencies: &'a [&'a str],
|
||||
/// Limit candidate packages to those that were uploaded prior to a given point in time (ISO 8601 format).
|
||||
/// Maps to uv's `exclude-newer`.
|
||||
pub max_dep_date: &'a str,
|
||||
@@ -125,9 +125,9 @@ impl<'a> InstalledProject<'a> {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the benchmark paths as `SystemPathBuf`
|
||||
pub fn check_paths(&self) -> &[&SystemPath] {
|
||||
&self.config.paths
|
||||
/// Get the benchmark paths
|
||||
pub fn check_paths(&self) -> &[&str] {
|
||||
self.config.paths
|
||||
}
|
||||
|
||||
/// Get the virtual environment path
|
||||
@@ -297,7 +297,7 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
|
||||
"--exclude-newer",
|
||||
checkout.project().max_dep_date,
|
||||
])
|
||||
.args(&checkout.project().dependencies);
|
||||
.args(checkout.project().dependencies);
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
|
||||
@@ -7,7 +7,8 @@ use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
DisplayDiagnostic, DisplayDiagnostics, DummyFileResolver, FileResolver, Input,
|
||||
ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::{Db, files::File};
|
||||
|
||||
@@ -1170,6 +1170,31 @@ pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
/// A stub implementation of [`FileResolver`] intended for testing.
|
||||
pub struct DummyFileResolver;
|
||||
|
||||
impl FileResolver for DummyFileResolver {
|
||||
fn path(&self, _file: File) -> &str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn input(&self, _file: File) -> Input {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn notebook_index(&self, _file: &UnifiedFile) -> Option<NotebookIndex> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_notebook(&self, _file: &UnifiedFile) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn current_directory(&self) -> &Path {
|
||||
Path::new(".")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -93,14 +93,39 @@ fn generate_markdown() -> String {
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let status_text = match lint.status() {
|
||||
ty_python_semantic::lint::LintStatus::Stable { since } => {
|
||||
format!(
|
||||
r#"Added in <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Preview { since } => {
|
||||
format!(
|
||||
r#"Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Deprecated { since, .. } => {
|
||||
format!(
|
||||
r#"Deprecated (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Removed { since, .. } => {
|
||||
format!(
|
||||
r#"Removed (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: [`{level}`](../rules.md#rule-levels "This lint has a default level of '{level}'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
{documentation}
|
||||
"#,
|
||||
level = lint.default_level(),
|
||||
|
||||
@@ -227,3 +227,32 @@ async def read_thing(query: str):
|
||||
@app.get("/things/{ thing_id : str }")
|
||||
async def read_thing(query: str):
|
||||
return {"query": query}
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20680
|
||||
# These should NOT trigger FAST003 because FastAPI doesn't recognize them as path parameters
|
||||
|
||||
# Non-ASCII characters in parameter name
|
||||
@app.get("/f1/{用户身份}")
|
||||
async def f1():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name
|
||||
@app.get("/f2/{x: str}")
|
||||
async def f2():
|
||||
return locals()
|
||||
|
||||
# Non-ASCII converter
|
||||
@app.get("/f3/{complex_number:ℂ}")
|
||||
async def f3():
|
||||
return locals()
|
||||
|
||||
# Mixed non-ASCII characters
|
||||
@app.get("/f4/{用户_id}")
|
||||
async def f4():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name with converter
|
||||
@app.get("/f5/{param: int}")
|
||||
async def f5():
|
||||
return locals()
|
||||
|
||||
@@ -33,3 +33,10 @@ class ShellConfig:
|
||||
|
||||
def run(self, username):
|
||||
Popen("true", shell={**self.shell_defaults, **self.fetch_shell_config(username)})
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
Popen("true", shell=(i for i in ()))
|
||||
Popen("true", shell=lambda: 0)
|
||||
Popen("true", shell=f"{b''}")
|
||||
x = 1
|
||||
Popen("true", shell=f"{x=}")
|
||||
|
||||
@@ -6,3 +6,19 @@ foo(shell=True)
|
||||
|
||||
foo(shell={**{}})
|
||||
foo(shell={**{**{}}})
|
||||
|
||||
# Truthy non-bool values for `shell`
|
||||
foo(shell=(i for i in ()))
|
||||
foo(shell=lambda: 0)
|
||||
|
||||
# f-strings guaranteed non-empty
|
||||
foo(shell=f"{b''}")
|
||||
x = 1
|
||||
foo(shell=f"{x=}")
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
foo(shell=(i for i in ()))
|
||||
foo(shell=lambda: 0)
|
||||
foo(shell=f"{b''}")
|
||||
x = 1
|
||||
foo(shell=f"{x=}")
|
||||
|
||||
@@ -9,3 +9,10 @@ os.system("tar cf foo.tar bar/*")
|
||||
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{}})
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{**{}}})
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
subprocess.Popen("chmod +w foo*", shell=(i for i in ()))
|
||||
subprocess.Popen("chmod +w foo*", shell=lambda: 0)
|
||||
subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
x = 1
|
||||
subprocess.Popen("chmod +w foo*", shell=f"{x=}")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# The lexer doesn't emit a string token if it's unterminated
|
||||
# The lexer emits a string token if it's unterminated
|
||||
"a" "b
|
||||
"a" "b" "c
|
||||
"a" """b
|
||||
c""" "d
|
||||
|
||||
# For f-strings, the `FStringRanges` won't contain the range for
|
||||
# This is also true for
|
||||
# unterminated f-strings.
|
||||
f"a" f"b
|
||||
f"a" f"b" f"c
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_implicit_concat.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_implicit_concat.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import logging
|
||||
|
||||
variablename = "value"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"a" f"b {variablename}")
|
||||
log.info("a " f"b {variablename}")
|
||||
log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
@@ -197,3 +197,10 @@ for x in {**a, **b} or [None]:
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7127
|
||||
def f(a: "'b' or 'c'"): ...
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20703
|
||||
print(f"{b''}" or "bar") # SIM222
|
||||
x = 1
|
||||
print(f"{x=}" or "bar") # SIM222
|
||||
(lambda: 1) or True # SIM222
|
||||
(i for i in range(1)) or "bar" # SIM222
|
||||
|
||||
@@ -18,3 +18,20 @@ def print_third_word(word: Hello.Text) -> None:
|
||||
|
||||
def print_fourth_word(word: Goodbye) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
import typing_extensions
|
||||
import typing_extensions as TypingExt
|
||||
from typing_extensions import Text as TextAlias
|
||||
|
||||
|
||||
def print_fifth_word(word: typing_extensions.Text) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
def print_sixth_word(word: TypingExt.Text) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
def print_seventh_word(word: TextAlias) -> None:
|
||||
print(word)
|
||||
|
||||
@@ -50,15 +50,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
||||
}
|
||||
}
|
||||
Stmt::Break(_) => {
|
||||
if checker.is_rule_enabled(Rule::BreakOutsideLoop) {
|
||||
pyflakes::rules::break_outside_loop(
|
||||
checker,
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
);
|
||||
}
|
||||
}
|
||||
Stmt::Continue(_) => {
|
||||
if checker.is_rule_enabled(Rule::ContinueOutsideLoop) {
|
||||
pyflakes::rules::continue_outside_loop(
|
||||
|
||||
@@ -697,6 +697,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::FutureFeatureNotDefined(name) => {
|
||||
// F407
|
||||
if self.is_rule_enabled(Rule::FutureFeatureNotDefined) {
|
||||
self.report_diagnostic(
|
||||
pyflakes::rules::FutureFeatureNotDefined { name },
|
||||
@@ -704,6 +705,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::BreakOutsideLoop => {
|
||||
// F701
|
||||
if self.is_rule_enabled(Rule::BreakOutsideLoop) {
|
||||
self.report_diagnostic(pyflakes::rules::BreakOutsideLoop, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||
@@ -811,19 +818,40 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn in_loop_context(&self) -> bool {
|
||||
let mut child = self.semantic.current_statement();
|
||||
|
||||
for parent in self.semantic.current_statements().skip(1) {
|
||||
match parent {
|
||||
Stmt::For(ast::StmtFor { orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { orelse, .. }) => {
|
||||
if !orelse.contains(child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for Checker<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_node(stmt);
|
||||
|
||||
// For functions, defer semantic syntax error checks until the body of the function is
|
||||
// visited
|
||||
if !stmt.is_function_def_stmt() {
|
||||
self.with_semantic_checker(|semantic, context| semantic.visit_stmt(stmt, context));
|
||||
}
|
||||
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_node(stmt);
|
||||
|
||||
// For Jupyter Notebooks, we'll reset the `IMPORT_BOUNDARY` flag when
|
||||
// we encounter a cell boundary.
|
||||
if self.source_type.is_ipynb()
|
||||
|
||||
@@ -11,8 +11,7 @@ use crate::settings::types::CompiledPerFileIgnoreList;
|
||||
pub fn get_cwd() -> &'static Path {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
static CWD: std::sync::LazyLock<PathBuf> = std::sync::LazyLock::new(|| PathBuf::from("."));
|
||||
&CWD
|
||||
Path::new(".")
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
path_absolutize::path_dedot::CWD.as_path()
|
||||
|
||||
@@ -265,3 +265,7 @@ pub(crate) const fn is_fix_read_whole_file_enabled(settings: &LinterSettings) ->
|
||||
pub(crate) const fn is_fix_write_whole_file_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
pub(crate) const fn is_typing_extensions_str_alias_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::iter::Peekable;
|
||||
use std::ops::Range;
|
||||
use std::str::CharIndices;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use regex::{CaptureMatches, Regex};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault};
|
||||
use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Fix;
|
||||
@@ -165,11 +165,6 @@ pub(crate) fn fastapi_unused_path_parameter(
|
||||
|
||||
// Check if any of the path parameters are not in the function signature.
|
||||
for (path_param, range) in path_params {
|
||||
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
|
||||
if !is_identifier(path_param) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the path parameter is already in the function or the dependency signature,
|
||||
// we don't need to do anything.
|
||||
if named_args.contains(&path_param) {
|
||||
@@ -461,15 +456,19 @@ fn parameter_alias<'a>(parameter: &'a Parameter, semantic: &SemanticModel) -> Op
|
||||
/// the parameter name. For example, `/{x}` is a valid parameter, but `/{ x }` is treated literally.
|
||||
#[derive(Debug)]
|
||||
struct PathParamIterator<'a> {
|
||||
input: &'a str,
|
||||
chars: Peekable<CharIndices<'a>>,
|
||||
inner: CaptureMatches<'a, 'a>,
|
||||
}
|
||||
|
||||
impl<'a> PathParamIterator<'a> {
|
||||
fn new(input: &'a str) -> Self {
|
||||
PathParamIterator {
|
||||
input,
|
||||
chars: input.char_indices().peekable(),
|
||||
/// Matches the Starlette pattern for path parameters with optional converters from
|
||||
/// <https://github.com/Kludex/starlette/blob/e18637c68e36d112b1983bc0c8b663681e6a4c50/starlette/routing.py#L121>
|
||||
static FASTAPI_PATH_PARAM_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::[a-zA-Z_][a-zA-Z0-9_]*)?\}").unwrap()
|
||||
});
|
||||
|
||||
Self {
|
||||
inner: FASTAPI_PATH_PARAM_REGEX.captures_iter(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,19 +477,10 @@ impl<'a> Iterator for PathParamIterator<'a> {
|
||||
type Item = (&'a str, Range<usize>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some((start, c)) = self.chars.next() {
|
||||
if c == '{' {
|
||||
if let Some((end, _)) = self.chars.by_ref().find(|&(_, ch)| ch == '}') {
|
||||
let param_content = &self.input[start + 1..end];
|
||||
// We ignore text after a colon, since those are path converters
|
||||
// See also: https://fastapi.tiangolo.com/tutorial/path-params/?h=path#path-convertor
|
||||
let param_name_end = param_content.find(':').unwrap_or(param_content.len());
|
||||
let param_name = ¶m_content[..param_name_end];
|
||||
|
||||
return Some((param_name, start..end + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
self.inner
|
||||
.next()
|
||||
// Extract the first capture group (the path parameter), but return the range of the
|
||||
// whole match (everything in braces and including the braces themselves).
|
||||
.and_then(|capture| Some((capture.get(1)?.as_str(), capture.get(0)?.range())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1091,9 +1091,12 @@ fn suspicious_function(
|
||||
] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherModeUsage, range),
|
||||
|
||||
// Mktemp
|
||||
["tempfile", "mktemp"] => {
|
||||
checker.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
|
||||
}
|
||||
["tempfile", "mktemp"] => checker
|
||||
.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
|
||||
.map(|mut diagnostic| {
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic
|
||||
}),
|
||||
|
||||
// Eval
|
||||
["" | "builtins", "eval"] => {
|
||||
|
||||
@@ -127,3 +127,44 @@ S602 `subprocess` call with `shell=True` identified, security issue
|
||||
21 |
|
||||
22 | # Check dict display with only double-starred expressions can be falsey.
|
||||
|
|
||||
|
||||
S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
--> S602.py:38:1
|
||||
|
|
||||
37 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
38 | Popen("true", shell=(i for i in ()))
|
||||
| ^^^^^
|
||||
39 | Popen("true", shell=lambda: 0)
|
||||
40 | Popen("true", shell=f"{b''}")
|
||||
|
|
||||
|
||||
S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
--> S602.py:39:1
|
||||
|
|
||||
37 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
38 | Popen("true", shell=(i for i in ()))
|
||||
39 | Popen("true", shell=lambda: 0)
|
||||
| ^^^^^
|
||||
40 | Popen("true", shell=f"{b''}")
|
||||
41 | x = 1
|
||||
|
|
||||
|
||||
S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
--> S602.py:40:1
|
||||
|
|
||||
38 | Popen("true", shell=(i for i in ()))
|
||||
39 | Popen("true", shell=lambda: 0)
|
||||
40 | Popen("true", shell=f"{b''}")
|
||||
| ^^^^^
|
||||
41 | x = 1
|
||||
42 | Popen("true", shell=f"{x=}")
|
||||
|
|
||||
|
||||
S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
--> S602.py:42:1
|
||||
|
|
||||
40 | Popen("true", shell=f"{b''}")
|
||||
41 | x = 1
|
||||
42 | Popen("true", shell=f"{x=}")
|
||||
| ^^^^^
|
||||
|
|
||||
|
||||
@@ -9,3 +9,85 @@ S604 Function call with `shell=True` parameter identified, security issue
|
||||
6 |
|
||||
7 | foo(shell={**{}})
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:11:1
|
||||
|
|
||||
10 | # Truthy non-bool values for `shell`
|
||||
11 | foo(shell=(i for i in ()))
|
||||
| ^^^
|
||||
12 | foo(shell=lambda: 0)
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:12:1
|
||||
|
|
||||
10 | # Truthy non-bool values for `shell`
|
||||
11 | foo(shell=(i for i in ()))
|
||||
12 | foo(shell=lambda: 0)
|
||||
| ^^^
|
||||
13 |
|
||||
14 | # f-strings guaranteed non-empty
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:15:1
|
||||
|
|
||||
14 | # f-strings guaranteed non-empty
|
||||
15 | foo(shell=f"{b''}")
|
||||
| ^^^
|
||||
16 | x = 1
|
||||
17 | foo(shell=f"{x=}")
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:17:1
|
||||
|
|
||||
15 | foo(shell=f"{b''}")
|
||||
16 | x = 1
|
||||
17 | foo(shell=f"{x=}")
|
||||
| ^^^
|
||||
18 |
|
||||
19 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:20:1
|
||||
|
|
||||
19 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
20 | foo(shell=(i for i in ()))
|
||||
| ^^^
|
||||
21 | foo(shell=lambda: 0)
|
||||
22 | foo(shell=f"{b''}")
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:21:1
|
||||
|
|
||||
19 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
20 | foo(shell=(i for i in ()))
|
||||
21 | foo(shell=lambda: 0)
|
||||
| ^^^
|
||||
22 | foo(shell=f"{b''}")
|
||||
23 | x = 1
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:22:1
|
||||
|
|
||||
20 | foo(shell=(i for i in ()))
|
||||
21 | foo(shell=lambda: 0)
|
||||
22 | foo(shell=f"{b''}")
|
||||
| ^^^
|
||||
23 | x = 1
|
||||
24 | foo(shell=f"{x=}")
|
||||
|
|
||||
|
||||
S604 Function call with truthy `shell` parameter identified, security issue
|
||||
--> S604.py:24:1
|
||||
|
|
||||
22 | foo(shell=f"{b''}")
|
||||
23 | x = 1
|
||||
24 | foo(shell=f"{x=}")
|
||||
| ^^^
|
||||
|
|
||||
|
||||
@@ -43,3 +43,44 @@ S609 Possible wildcard injection in call due to `*` usage
|
||||
9 |
|
||||
10 | subprocess.Popen(["chmod", "+w", "*.py"], shell={**{}})
|
||||
|
|
||||
|
||||
S609 Possible wildcard injection in call due to `*` usage
|
||||
--> S609.py:14:18
|
||||
|
|
||||
13 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
14 | subprocess.Popen("chmod +w foo*", shell=(i for i in ()))
|
||||
| ^^^^^^^^^^^^^^^
|
||||
15 | subprocess.Popen("chmod +w foo*", shell=lambda: 0)
|
||||
16 | subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
|
|
||||
|
||||
S609 Possible wildcard injection in call due to `*` usage
|
||||
--> S609.py:15:18
|
||||
|
|
||||
13 | # Additional truthiness cases for generator, lambda, and f-strings
|
||||
14 | subprocess.Popen("chmod +w foo*", shell=(i for i in ()))
|
||||
15 | subprocess.Popen("chmod +w foo*", shell=lambda: 0)
|
||||
| ^^^^^^^^^^^^^^^
|
||||
16 | subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
17 | x = 1
|
||||
|
|
||||
|
||||
S609 Possible wildcard injection in call due to `*` usage
|
||||
--> S609.py:16:18
|
||||
|
|
||||
14 | subprocess.Popen("chmod +w foo*", shell=(i for i in ()))
|
||||
15 | subprocess.Popen("chmod +w foo*", shell=lambda: 0)
|
||||
16 | subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
17 | x = 1
|
||||
18 | subprocess.Popen("chmod +w foo*", shell=f"{x=}")
|
||||
|
|
||||
|
||||
S609 Possible wildcard injection in call due to `*` usage
|
||||
--> S609.py:18:18
|
||||
|
|
||||
16 | subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
17 | x = 1
|
||||
18 | subprocess.Popen("chmod +w foo*", shell=f"{x=}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -188,16 +188,10 @@ fn move_initialization(
|
||||
content.push_str(stylist.line_ending().as_str());
|
||||
content.push_str(stylist.indentation());
|
||||
if is_b006_unsafe_fix_preserve_assignment_expr_enabled(checker.settings()) {
|
||||
let annotation = if let Some(ann) = parameter.annotation() {
|
||||
format!(": {}", locator.slice(ann))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let _ = write!(
|
||||
&mut content,
|
||||
"{}{} = {}",
|
||||
"{} = {}",
|
||||
parameter.parameter.name(),
|
||||
annotation,
|
||||
locator.slice(
|
||||
parenthesized_range(
|
||||
default.into(),
|
||||
|
||||
@@ -16,7 +16,7 @@ help: Replace with `None`; initialize within function
|
||||
5 + def import_module_wrong(value: dict[str, str] = None):
|
||||
6 | import os
|
||||
7 + if value is None:
|
||||
8 + value: dict[str, str] = {}
|
||||
8 + value = {}
|
||||
9 |
|
||||
10 |
|
||||
11 | def import_module_with_values_wrong(value: dict[str, str] = {}):
|
||||
@@ -38,7 +38,7 @@ help: Replace with `None`; initialize within function
|
||||
10 | import os
|
||||
11 |
|
||||
12 + if value is None:
|
||||
13 + value: dict[str, str] = {}
|
||||
13 + value = {}
|
||||
14 | return 2
|
||||
15 |
|
||||
16 |
|
||||
@@ -62,7 +62,7 @@ help: Replace with `None`; initialize within function
|
||||
17 | import sys
|
||||
18 | import itertools
|
||||
19 + if value is None:
|
||||
20 + value: dict[str, str] = {}
|
||||
20 + value = {}
|
||||
21 |
|
||||
22 |
|
||||
23 | def from_import_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -83,7 +83,7 @@ help: Replace with `None`; initialize within function
|
||||
21 + def from_import_module_wrong(value: dict[str, str] = None):
|
||||
22 | from os import path
|
||||
23 + if value is None:
|
||||
24 + value: dict[str, str] = {}
|
||||
24 + value = {}
|
||||
25 |
|
||||
26 |
|
||||
27 | def from_imports_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -106,7 +106,7 @@ help: Replace with `None`; initialize within function
|
||||
26 | from os import path
|
||||
27 | from sys import version_info
|
||||
28 + if value is None:
|
||||
29 + value: dict[str, str] = {}
|
||||
29 + value = {}
|
||||
30 |
|
||||
31 |
|
||||
32 | def import_and_from_imports_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -129,7 +129,7 @@ help: Replace with `None`; initialize within function
|
||||
31 | import os
|
||||
32 | from sys import version_info
|
||||
33 + if value is None:
|
||||
34 + value: dict[str, str] = {}
|
||||
34 + value = {}
|
||||
35 |
|
||||
36 |
|
||||
37 | def import_docstring_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -152,7 +152,7 @@ help: Replace with `None`; initialize within function
|
||||
36 | """Docstring"""
|
||||
37 | import os
|
||||
38 + if value is None:
|
||||
39 + value: dict[str, str] = {}
|
||||
39 + value = {}
|
||||
40 |
|
||||
41 |
|
||||
42 | def import_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -175,7 +175,7 @@ help: Replace with `None`; initialize within function
|
||||
41 | """Docstring"""
|
||||
42 | import os; import sys
|
||||
43 + if value is None:
|
||||
44 + value: dict[str, str] = {}
|
||||
44 + value = {}
|
||||
45 |
|
||||
46 |
|
||||
47 | def import_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -197,7 +197,7 @@ help: Replace with `None`; initialize within function
|
||||
45 + def import_module_wrong(value: dict[str, str] = None):
|
||||
46 | """Docstring"""
|
||||
47 + if value is None:
|
||||
48 + value: dict[str, str] = {}
|
||||
48 + value = {}
|
||||
49 | import os; import sys; x = 1
|
||||
50 |
|
||||
51 |
|
||||
@@ -220,7 +220,7 @@ help: Replace with `None`; initialize within function
|
||||
51 | """Docstring"""
|
||||
52 | import os; import sys
|
||||
53 + if value is None:
|
||||
54 + value: dict[str, str] = {}
|
||||
54 + value = {}
|
||||
55 |
|
||||
56 |
|
||||
57 | def import_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -241,7 +241,7 @@ help: Replace with `None`; initialize within function
|
||||
55 + def import_module_wrong(value: dict[str, str] = None):
|
||||
56 | import os; import sys
|
||||
57 + if value is None:
|
||||
58 + value: dict[str, str] = {}
|
||||
58 + value = {}
|
||||
59 |
|
||||
60 |
|
||||
61 | def import_module_wrong(value: dict[str, str] = {}):
|
||||
@@ -261,7 +261,7 @@ help: Replace with `None`; initialize within function
|
||||
- def import_module_wrong(value: dict[str, str] = {}):
|
||||
59 + def import_module_wrong(value: dict[str, str] = None):
|
||||
60 + if value is None:
|
||||
61 + value: dict[str, str] = {}
|
||||
61 + value = {}
|
||||
62 | import os; import sys; x = 1
|
||||
63 |
|
||||
64 |
|
||||
@@ -282,7 +282,7 @@ help: Replace with `None`; initialize within function
|
||||
63 + def import_module_wrong(value: dict[str, str] = None):
|
||||
64 | import os; import sys
|
||||
65 + if value is None:
|
||||
66 + value: dict[str, str] = {}
|
||||
66 + value = {}
|
||||
67 |
|
||||
68 |
|
||||
69 | def import_module_wrong(value: dict[str, str] = {}): import os
|
||||
|
||||
@@ -51,7 +51,7 @@ help: Replace with `None`; initialize within function
|
||||
10 + def baz(a: list = None):
|
||||
11 | """This one raises a different exception"""
|
||||
12 + if a is None:
|
||||
13 + a: list = []
|
||||
13 + a = []
|
||||
14 | raise IndexError()
|
||||
15 |
|
||||
16 |
|
||||
|
||||
@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for usage of `datetime.date.fromtimestamp()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// Python date objects are naive, that is, not timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.date.fromtimestamp(ts)` returns a naive datetime object.
|
||||
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` to create a
|
||||
/// timezone-aware object.
|
||||
/// `datetime.date.fromtimestamp(ts)` returns a naive date object.
|
||||
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...).date()` to
|
||||
/// create a timezone-aware datetime object and retrieve its date component.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -32,14 +32,14 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc).date()
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC).date()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -11,14 +11,15 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for usage of `datetime.date.today()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// Python date objects are naive, that is, not timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.date.today` returns a naive datetime object. Instead, use
|
||||
/// `datetime.datetime.now(tz=...).date()` to create a timezone-aware object.
|
||||
/// `datetime.date.today` returns a naive date object without taking timezones
|
||||
/// into account. Instead, use `datetime.datetime.now(tz=...).date()` to
|
||||
/// create a timezone-aware object and retrieve its date component.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -42,6 +42,9 @@ use crate::rules::flake8_datetimez::helpers;
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CallDatetimeToday;
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ use crate::rules::flake8_datetimez::helpers::{self, DatetimeModuleAntipattern};
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CallDatetimeWithoutTzinfo(DatetimeModuleAntipattern);
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// datetime.datetime.max.replace(tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DatetimeMinMax {
|
||||
min_max: MinMax,
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
||||
---
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:2:5
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC_syntax_error.py:2:1
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
| ^^
|
||||
| ^^^^^^
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:2:7
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:2:5
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
| ^
|
||||
| ^^
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
|
|
||||
@@ -24,7 +25,7 @@ invalid-syntax: Expected a statement
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC_syntax_error.py:3:1
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
| ^^^^^^^
|
||||
@@ -33,24 +34,25 @@ ISC001 Implicitly concatenated string literals on one line
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:3:9
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC_syntax_error.py:3:5
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
| ^^
|
||||
| ^^^^^^
|
||||
4 | "a" """b
|
||||
5 | c""" "d
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:3:11
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:3:9
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
| ^
|
||||
| ^^
|
||||
4 | "a" """b
|
||||
5 | c""" "d
|
||||
|
|
||||
@@ -64,7 +66,21 @@ ISC001 Implicitly concatenated string literals on one line
|
||||
5 | | c""" "d
|
||||
| |____^
|
||||
6 |
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC_syntax_error.py:4:5
|
||||
|
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
| _____^
|
||||
5 | | c""" "d
|
||||
| |_______^
|
||||
6 |
|
||||
7 | # This is also true for
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
@@ -76,24 +92,13 @@ invalid-syntax: missing closing quote in string literal
|
||||
5 | c""" "d
|
||||
| ^^
|
||||
6 |
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:5:8
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
5 | c""" "d
|
||||
| ^
|
||||
6 |
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
|
|
||||
|
||||
invalid-syntax: f-string: unterminated string
|
||||
--> ISC_syntax_error.py:9:8
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
| ^
|
||||
@@ -104,7 +109,7 @@ invalid-syntax: f-string: unterminated string
|
||||
invalid-syntax: Expected FStringEnd, found newline
|
||||
--> ISC_syntax_error.py:9:9
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
| ^
|
||||
@@ -183,14 +188,6 @@ invalid-syntax: f-string: unterminated triple-quoted string
|
||||
| |__^
|
||||
|
|
||||
|
||||
invalid-syntax: unexpected EOF while parsing
|
||||
--> ISC_syntax_error.py:30:1
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
| ^
|
||||
|
|
||||
|
||||
invalid-syntax: f-string: unterminated string
|
||||
--> ISC_syntax_error.py:30:1
|
||||
|
|
||||
|
||||
@@ -4,27 +4,17 @@ source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:2:5
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
| ^^
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:2:7
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
| ^
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
|
|
||||
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:3:9
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
1 | # The lexer emits a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
| ^^
|
||||
@@ -32,17 +22,6 @@ invalid-syntax: missing closing quote in string literal
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:3:11
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
3 | "a" "b" "c
|
||||
| ^
|
||||
4 | "a" """b
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
invalid-syntax: missing closing quote in string literal
|
||||
--> ISC_syntax_error.py:5:6
|
||||
|
|
||||
@@ -51,24 +30,13 @@ invalid-syntax: missing closing quote in string literal
|
||||
5 | c""" "d
|
||||
| ^^
|
||||
6 |
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> ISC_syntax_error.py:5:8
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
5 | c""" "d
|
||||
| ^
|
||||
6 |
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
|
|
||||
|
||||
invalid-syntax: f-string: unterminated string
|
||||
--> ISC_syntax_error.py:9:8
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
| ^
|
||||
@@ -79,7 +47,7 @@ invalid-syntax: f-string: unterminated string
|
||||
invalid-syntax: Expected FStringEnd, found newline
|
||||
--> ISC_syntax_error.py:9:9
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
7 | # This is also true for
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
| ^
|
||||
@@ -133,14 +101,6 @@ invalid-syntax: f-string: unterminated triple-quoted string
|
||||
| |__^
|
||||
|
|
||||
|
||||
invalid-syntax: unexpected EOF while parsing
|
||||
--> ISC_syntax_error.py:30:1
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
| ^
|
||||
|
|
||||
|
||||
invalid-syntax: f-string: unterminated string
|
||||
--> ISC_syntax_error.py:30:1
|
||||
|
|
||||
|
||||
@@ -23,6 +23,7 @@ mod tests {
|
||||
#[test_case(Path::new("G003.py"))]
|
||||
#[test_case(Path::new("G004.py"))]
|
||||
#[test_case(Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Path::new("G004_implicit_concat.py"))]
|
||||
#[test_case(Path::new("G010.py"))]
|
||||
#[test_case(Path::new("G101_1.py"))]
|
||||
#[test_case(Path::new("G101_2.py"))]
|
||||
@@ -52,6 +53,7 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_implicit_concat.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -42,38 +42,52 @@ fn logging_f_string(
|
||||
// Default to double quotes if we can't determine it.
|
||||
let quote_str = f_string
|
||||
.value
|
||||
.f_strings()
|
||||
.iter()
|
||||
.map(|part| match part {
|
||||
ast::FStringPart::Literal(literal) => literal.flags.quote_str(),
|
||||
ast::FStringPart::FString(f) => f.flags.quote_str(),
|
||||
})
|
||||
.next()
|
||||
.map(|f| f.flags.quote_str())
|
||||
.unwrap_or("\"");
|
||||
|
||||
for f in f_string.value.f_strings() {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
for part in &f_string.value {
|
||||
match part {
|
||||
ast::FStringPart::Literal(literal) => {
|
||||
let literal_text = literal.as_str();
|
||||
if literal_text.contains('%') {
|
||||
return;
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
format_string.push_str(literal_text);
|
||||
}
|
||||
ast::FStringPart::FString(f) => {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 50
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 71
|
||||
---
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
3 | variablename = "value"
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
- log.info(f"a" f"b {variablename}")
|
||||
6 + log.info("ab %s", variablename)
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
- log.info("a " f"b {variablename}")
|
||||
7 + log.info("a b %s", variablename)
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
- log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
8 + log.info("prefix middle %s suffix", variablename)
|
||||
@@ -74,7 +74,8 @@ pub(crate) fn bytestring_attribute(checker: &Checker, attribute: &Expr) {
|
||||
["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc,
|
||||
_ => return,
|
||||
};
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
|
||||
let mut diagnostic = checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
}
|
||||
|
||||
/// PYI057
|
||||
@@ -94,7 +95,9 @@ pub(crate) fn bytestring_import(checker: &Checker, import_from: &ast::StmtImport
|
||||
|
||||
for name in names {
|
||||
if name.name.as_str() == "ByteString" {
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,7 +898,9 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato
|
||||
/// PT020
|
||||
fn check_fixture_decorator_name(checker: &Checker, decorator: &Decorator) {
|
||||
if is_pytest_yield_fixture(decorator, checker.semantic()) {
|
||||
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1062,3 +1062,77 @@ help: Replace with `"bar"`
|
||||
170 |
|
||||
171 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM222 [*] Use `f"{b''}"` instead of `f"{b''}" or ...`
|
||||
--> SIM222.py:202:7
|
||||
|
|
||||
201 | # https://github.com/astral-sh/ruff/issues/20703
|
||||
202 | print(f"{b''}" or "bar") # SIM222
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
|
|
||||
help: Replace with `f"{b''}"`
|
||||
199 | def f(a: "'b' or 'c'"): ...
|
||||
200 |
|
||||
201 | # https://github.com/astral-sh/ruff/issues/20703
|
||||
- print(f"{b''}" or "bar") # SIM222
|
||||
202 + print(f"{b''}") # SIM222
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM222 [*] Use `f"{x=}"` instead of `f"{x=}" or ...`
|
||||
--> SIM222.py:204:7
|
||||
|
|
||||
202 | print(f"{b''}" or "bar") # SIM222
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
206 | (i for i in range(1)) or "bar" # SIM222
|
||||
|
|
||||
help: Replace with `f"{x=}"`
|
||||
201 | # https://github.com/astral-sh/ruff/issues/20703
|
||||
202 | print(f"{b''}" or "bar") # SIM222
|
||||
203 | x = 1
|
||||
- print(f"{x=}" or "bar") # SIM222
|
||||
204 + print(f"{x=}") # SIM222
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
206 | (i for i in range(1)) or "bar" # SIM222
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM222 [*] Use `lambda: 1` instead of `lambda: 1 or ...`
|
||||
--> SIM222.py:205:1
|
||||
|
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
206 | (i for i in range(1)) or "bar" # SIM222
|
||||
|
|
||||
help: Replace with `lambda: 1`
|
||||
202 | print(f"{b''}" or "bar") # SIM222
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
- (lambda: 1) or True # SIM222
|
||||
205 + lambda: 1 # SIM222
|
||||
206 | (i for i in range(1)) or "bar" # SIM222
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM222 [*] Use `(i for i in range(1))` instead of `(i for i in range(1)) or ...`
|
||||
--> SIM222.py:206:1
|
||||
|
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
206 | (i for i in range(1)) or "bar" # SIM222
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace with `(i for i in range(1))`
|
||||
203 | x = 1
|
||||
204 | print(f"{x=}" or "bar") # SIM222
|
||||
205 | (lambda: 1) or True # SIM222
|
||||
- (i for i in range(1)) or "bar" # SIM222
|
||||
206 + (i for i in range(1)) # SIM222
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -223,7 +223,7 @@ enum Argumentable {
|
||||
|
||||
impl Argumentable {
|
||||
fn check_for(self, checker: &Checker, name: String, range: TextRange) {
|
||||
match self {
|
||||
let mut diagnostic = match self {
|
||||
Self::Function => checker.report_diagnostic(UnusedFunctionArgument { name }, range),
|
||||
Self::Method => checker.report_diagnostic(UnusedMethodArgument { name }, range),
|
||||
Self::ClassMethod => {
|
||||
@@ -234,6 +234,7 @@ impl Argumentable {
|
||||
}
|
||||
Self::Lambda => checker.report_diagnostic(UnusedLambdaArgument { name }, range),
|
||||
};
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
}
|
||||
|
||||
const fn rule_code(self) -> Rule {
|
||||
|
||||
@@ -80,6 +80,7 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) {
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("numpy", replacement),
|
||||
|
||||
@@ -80,6 +80,7 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) {
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
let type_name = match type_name {
|
||||
"unicode" => "str",
|
||||
_ => type_name,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{Violation, checkers::ast::Checker};
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `break` statements outside of loops.
|
||||
@@ -29,28 +26,3 @@ impl Violation for BreakOutsideLoop {
|
||||
"`break` outside loop".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// F701
|
||||
pub(crate) fn break_outside_loop<'a>(
|
||||
checker: &Checker,
|
||||
stmt: &'a Stmt,
|
||||
parents: &mut impl Iterator<Item = &'a Stmt>,
|
||||
) {
|
||||
let mut child = stmt;
|
||||
for parent in parents {
|
||||
match parent {
|
||||
Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => {
|
||||
if !orelse.contains(child) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(BreakOutsideLoop, stmt.range());
|
||||
}
|
||||
|
||||
@@ -23,16 +23,17 @@ invalid-syntax: missing closing quote in string literal
|
||||
9 | # Unterminated f-string
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> invalid_characters_syntax_error.py:7:7
|
||||
PLE2510 Invalid unescaped character backspace, use "\b" instead
|
||||
--> invalid_characters_syntax_error.py:7:6
|
||||
|
|
||||
5 | b = '␈'
|
||||
6 | # Unterminated string
|
||||
7 | b = '␈
|
||||
| ^
|
||||
| ^
|
||||
8 | b = '␈'
|
||||
9 | # Unterminated f-string
|
||||
|
|
||||
help: Replace with escape sequence
|
||||
|
||||
PLE2510 Invalid unescaped character backspace, use "\b" instead
|
||||
--> invalid_characters_syntax_error.py:8:6
|
||||
@@ -46,6 +47,18 @@ PLE2510 Invalid unescaped character backspace, use "\b" instead
|
||||
|
|
||||
help: Replace with escape sequence
|
||||
|
||||
PLE2510 Invalid unescaped character backspace, use "\b" instead
|
||||
--> invalid_characters_syntax_error.py:10:7
|
||||
|
|
||||
8 | b = '␈'
|
||||
9 | # Unterminated f-string
|
||||
10 | b = f'␈
|
||||
| ^
|
||||
11 | b = f'␈'
|
||||
12 | # Implicitly concatenated
|
||||
|
|
||||
help: Replace with escape sequence
|
||||
|
||||
invalid-syntax: f-string: unterminated string
|
||||
--> invalid_characters_syntax_error.py:10:7
|
||||
|
|
||||
@@ -109,11 +122,12 @@ invalid-syntax: missing closing quote in string literal
|
||||
| ^^
|
||||
|
|
||||
|
||||
invalid-syntax: Expected a statement
|
||||
--> invalid_characters_syntax_error.py:13:16
|
||||
PLE2510 Invalid unescaped character backspace, use "\b" instead
|
||||
--> invalid_characters_syntax_error.py:13:15
|
||||
|
|
||||
11 | b = f'␈'
|
||||
12 | # Implicitly concatenated
|
||||
13 | b = '␈' f'␈' '␈
|
||||
| ^
|
||||
| ^
|
||||
|
|
||||
help: Replace with escape sequence
|
||||
|
||||
@@ -126,6 +126,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Rule::SuperCallWithParameters, Path::new("UP008.py"))]
|
||||
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
|
||||
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}__preview", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -6,7 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::str::{leading_quote, trailing_quote};
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword};
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword, StringFlags};
|
||||
use ruff_python_literal::format::{
|
||||
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
|
||||
};
|
||||
@@ -430,7 +430,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
||||
// dot is the start of an attribute access.
|
||||
break token.start();
|
||||
}
|
||||
TokenKind::String => {
|
||||
TokenKind::String if !token.unwrap_string_flags().is_unclosed() => {
|
||||
match FStringConversion::try_convert(token.range(), &mut summary, checker.locator())
|
||||
{
|
||||
// If the format string contains side effects that would need to be repeated,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use ruff_python_ast::Expr;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_typing_extensions_str_alias_enabled;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `typing.Text`.
|
||||
///
|
||||
/// In preview mode, also checks for `typing_extensions.Text`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `typing.Text` is an alias for `str`, and only exists for Python 2
|
||||
/// compatibility. As of Python 3.11, `typing.Text` is deprecated. Use `str`
|
||||
@@ -30,14 +34,16 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Text`](https://docs.python.org/3/library/typing.html#typing.Text)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct TypingTextStrAlias;
|
||||
pub(crate) struct TypingTextStrAlias {
|
||||
module: TypingModule,
|
||||
}
|
||||
|
||||
impl Violation for TypingTextStrAlias {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`typing.Text` is deprecated, use `str`".to_string()
|
||||
format!("`{}.Text` is deprecated, use `str`", self.module)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
@@ -47,16 +53,26 @@ impl Violation for TypingTextStrAlias {
|
||||
|
||||
/// UP019
|
||||
pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) {
|
||||
if !checker.semantic().seen_module(Modules::TYPING) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.seen_module(Modules::TYPING | Modules::TYPING_EXTENSIONS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["typing", "Text"]))
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias, expr.range());
|
||||
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) {
|
||||
let segments = qualified_name.segments();
|
||||
let module = match segments {
|
||||
["typing", "Text"] => TypingModule::Typing,
|
||||
["typing_extensions", "Text"]
|
||||
if is_typing_extensions_str_alias_enabled(checker.settings()) =>
|
||||
{
|
||||
TypingModule::TypingExtensions
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias { module }, expr.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol(
|
||||
@@ -71,3 +87,18 @@ pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum TypingModule {
|
||||
Typing,
|
||||
TypingExtensions,
|
||||
}
|
||||
|
||||
impl Display for TypingModule {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TypingModule::Typing => f.write_str("typing"),
|
||||
TypingModule::TypingExtensions => f.write_str("typing_extensions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,3 +66,5 @@ help: Replace with `str`
|
||||
- def print_fourth_word(word: Goodbye) -> None:
|
||||
19 + def print_fourth_word(word: str) -> None:
|
||||
20 | print(word)
|
||||
21 |
|
||||
22 |
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP019 [*] `typing.Text` is deprecated, use `str`
|
||||
--> UP019.py:7:22
|
||||
|
|
||||
7 | def print_word(word: Text) -> None:
|
||||
| ^^^^
|
||||
8 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
4 | from typing import Text as Goodbye
|
||||
5 |
|
||||
6 |
|
||||
- def print_word(word: Text) -> None:
|
||||
7 + def print_word(word: str) -> None:
|
||||
8 | print(word)
|
||||
9 |
|
||||
10 |
|
||||
|
||||
UP019 [*] `typing.Text` is deprecated, use `str`
|
||||
--> UP019.py:11:29
|
||||
|
|
||||
11 | def print_second_word(word: typing.Text) -> None:
|
||||
| ^^^^^^^^^^^
|
||||
12 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
8 | print(word)
|
||||
9 |
|
||||
10 |
|
||||
- def print_second_word(word: typing.Text) -> None:
|
||||
11 + def print_second_word(word: str) -> None:
|
||||
12 | print(word)
|
||||
13 |
|
||||
14 |
|
||||
|
||||
UP019 [*] `typing.Text` is deprecated, use `str`
|
||||
--> UP019.py:15:28
|
||||
|
|
||||
15 | def print_third_word(word: Hello.Text) -> None:
|
||||
| ^^^^^^^^^^
|
||||
16 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
12 | print(word)
|
||||
13 |
|
||||
14 |
|
||||
- def print_third_word(word: Hello.Text) -> None:
|
||||
15 + def print_third_word(word: str) -> None:
|
||||
16 | print(word)
|
||||
17 |
|
||||
18 |
|
||||
|
||||
UP019 [*] `typing.Text` is deprecated, use `str`
|
||||
--> UP019.py:19:29
|
||||
|
|
||||
19 | def print_fourth_word(word: Goodbye) -> None:
|
||||
| ^^^^^^^
|
||||
20 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
16 | print(word)
|
||||
17 |
|
||||
18 |
|
||||
- def print_fourth_word(word: Goodbye) -> None:
|
||||
19 + def print_fourth_word(word: str) -> None:
|
||||
20 | print(word)
|
||||
21 |
|
||||
22 |
|
||||
|
||||
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
|
||||
--> UP019.py:28:28
|
||||
|
|
||||
28 | def print_fifth_word(word: typing_extensions.Text) -> None:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
25 | from typing_extensions import Text as TextAlias
|
||||
26 |
|
||||
27 |
|
||||
- def print_fifth_word(word: typing_extensions.Text) -> None:
|
||||
28 + def print_fifth_word(word: str) -> None:
|
||||
29 | print(word)
|
||||
30 |
|
||||
31 |
|
||||
|
||||
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
|
||||
--> UP019.py:32:28
|
||||
|
|
||||
32 | def print_sixth_word(word: TypingExt.Text) -> None:
|
||||
| ^^^^^^^^^^^^^^
|
||||
33 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
29 | print(word)
|
||||
30 |
|
||||
31 |
|
||||
- def print_sixth_word(word: TypingExt.Text) -> None:
|
||||
32 + def print_sixth_word(word: str) -> None:
|
||||
33 | print(word)
|
||||
34 |
|
||||
35 |
|
||||
|
||||
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
|
||||
--> UP019.py:36:30
|
||||
|
|
||||
36 | def print_seventh_word(word: TextAlias) -> None:
|
||||
| ^^^^^^^^^
|
||||
37 | print(word)
|
||||
|
|
||||
help: Replace with `str`
|
||||
33 | print(word)
|
||||
34 |
|
||||
35 |
|
||||
- def print_seventh_word(word: TextAlias) -> None:
|
||||
36 + def print_seventh_word(word: str) -> None:
|
||||
37 | print(word)
|
||||
@@ -48,7 +48,7 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
/// element. As such, any side effects that occur during iteration will be
|
||||
/// delayed.
|
||||
/// 2. Second, accessing members of a collection via square bracket notation
|
||||
/// `[0]` of the `pop()` function will raise `IndexError` if the collection
|
||||
/// `[0]` or the `pop()` function will raise `IndexError` if the collection
|
||||
/// is empty, while `next(iter(...))` will raise `StopIteration`.
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::parenthesize::parenthesized_range;
|
||||
use crate::statement_visitor::StatementVisitor;
|
||||
use crate::visitor::Visitor;
|
||||
use crate::{
|
||||
self as ast, Arguments, AtomicNodeIndex, CmpOp, DictItem, ExceptHandler, Expr,
|
||||
self as ast, Arguments, AtomicNodeIndex, CmpOp, DictItem, ExceptHandler, Expr, ExprNoneLiteral,
|
||||
InterpolatedStringElement, MatchCase, Operator, Pattern, Stmt, TypeParam,
|
||||
};
|
||||
use crate::{AnyNodeRef, ExprContext};
|
||||
@@ -1219,6 +1219,8 @@ impl Truthiness {
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
match expr {
|
||||
Expr::Lambda(_) => Self::Truthy,
|
||||
Expr::Generator(_) => Self::Truthy,
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
if value.is_empty() {
|
||||
Self::Falsey
|
||||
@@ -1388,7 +1390,9 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
Expr::FString(f_string) => is_non_empty_f_string(f_string),
|
||||
// These literals may or may not be empty.
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
|
||||
// Confusingly, f"{b""}" renders as the string 'b""', which is non-empty.
|
||||
// Therefore, any bytes interpolation is guaranteed non-empty when stringified.
|
||||
Expr::BytesLiteral(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1397,7 +1401,9 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
ast::FStringPart::FString(f_string) => {
|
||||
f_string.elements.iter().all(|element| match element {
|
||||
InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
|
||||
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
|
||||
InterpolatedStringElement::Interpolation(f_string) => {
|
||||
f_string.debug_text.is_some() || inner(&f_string.expression)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1493,7 +1499,7 @@ pub fn pep_604_optional(expr: &Expr) -> Expr {
|
||||
ast::ExprBinOp {
|
||||
left: Box::new(expr.clone()),
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(Expr::NoneLiteral(ast::ExprNoneLiteral::default())),
|
||||
right: Box::new(Expr::NoneLiteral(ExprNoneLiteral::default())),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
|
||||
@@ -735,6 +735,8 @@ pub trait StringFlags: Copy {
|
||||
|
||||
fn prefix(self) -> AnyStringPrefix;
|
||||
|
||||
fn is_unclosed(self) -> bool;
|
||||
|
||||
/// Is the string triple-quoted, i.e.,
|
||||
/// does it begin and end with three consecutive quote characters?
|
||||
fn is_triple_quoted(self) -> bool {
|
||||
@@ -779,6 +781,7 @@ pub trait StringFlags: Copy {
|
||||
|
||||
fn as_any_string_flags(self) -> AnyStringFlags {
|
||||
AnyStringFlags::new(self.prefix(), self.quote_style(), self.triple_quotes())
|
||||
.with_unclosed(self.is_unclosed())
|
||||
}
|
||||
|
||||
fn display_contents(self, contents: &str) -> DisplayFlags<'_> {
|
||||
@@ -829,6 +832,10 @@ bitflags! {
|
||||
/// for why we track the casing of the `r` prefix,
|
||||
/// but not for any other prefix
|
||||
const R_PREFIX_UPPER = 1 << 3;
|
||||
|
||||
/// The f-string is unclosed, meaning it is missing a closing quote.
|
||||
/// For example: `f"{bar`
|
||||
const UNCLOSED = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,6 +894,12 @@ impl FStringFlags {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unclosed(mut self, unclosed: bool) -> Self {
|
||||
self.0.set(InterpolatedStringFlagsInner::UNCLOSED, unclosed);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_prefix(mut self, prefix: FStringPrefix) -> Self {
|
||||
match prefix {
|
||||
@@ -984,6 +997,12 @@ impl TStringFlags {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unclosed(mut self, unclosed: bool) -> Self {
|
||||
self.0.set(InterpolatedStringFlagsInner::UNCLOSED, unclosed);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_prefix(mut self, prefix: TStringPrefix) -> Self {
|
||||
match prefix {
|
||||
@@ -1051,6 +1070,10 @@ impl StringFlags for FStringFlags {
|
||||
fn prefix(self) -> AnyStringPrefix {
|
||||
AnyStringPrefix::Format(self.prefix())
|
||||
}
|
||||
|
||||
fn is_unclosed(self) -> bool {
|
||||
self.0.intersects(InterpolatedStringFlagsInner::UNCLOSED)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FStringFlags {
|
||||
@@ -1059,6 +1082,7 @@ impl fmt::Debug for FStringFlags {
|
||||
.field("quote_style", &self.quote_style())
|
||||
.field("prefix", &self.prefix())
|
||||
.field("triple_quoted", &self.is_triple_quoted())
|
||||
.field("unclosed", &self.is_unclosed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1090,6 +1114,10 @@ impl StringFlags for TStringFlags {
|
||||
fn prefix(self) -> AnyStringPrefix {
|
||||
AnyStringPrefix::Template(self.prefix())
|
||||
}
|
||||
|
||||
fn is_unclosed(self) -> bool {
|
||||
self.0.intersects(InterpolatedStringFlagsInner::UNCLOSED)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TStringFlags {
|
||||
@@ -1098,6 +1126,7 @@ impl fmt::Debug for TStringFlags {
|
||||
.field("quote_style", &self.quote_style())
|
||||
.field("prefix", &self.prefix())
|
||||
.field("triple_quoted", &self.is_triple_quoted())
|
||||
.field("unclosed", &self.is_unclosed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1427,6 +1456,9 @@ bitflags! {
|
||||
|
||||
/// The string was deemed invalid by the parser.
|
||||
const INVALID = 1 << 5;
|
||||
|
||||
/// The string literal misses the matching closing quote(s).
|
||||
const UNCLOSED = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1479,6 +1511,12 @@ impl StringLiteralFlags {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unclosed(mut self, unclosed: bool) -> Self {
|
||||
self.0.set(StringLiteralFlagsInner::UNCLOSED, unclosed);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_prefix(self, prefix: StringLiteralPrefix) -> Self {
|
||||
let StringLiteralFlags(flags) = self;
|
||||
@@ -1560,6 +1598,10 @@ impl StringFlags for StringLiteralFlags {
|
||||
fn prefix(self) -> AnyStringPrefix {
|
||||
AnyStringPrefix::Regular(self.prefix())
|
||||
}
|
||||
|
||||
fn is_unclosed(self) -> bool {
|
||||
self.0.intersects(StringLiteralFlagsInner::UNCLOSED)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StringLiteralFlags {
|
||||
@@ -1568,6 +1610,7 @@ impl fmt::Debug for StringLiteralFlags {
|
||||
.field("quote_style", &self.quote_style())
|
||||
.field("prefix", &self.prefix())
|
||||
.field("triple_quoted", &self.is_triple_quoted())
|
||||
.field("unclosed", &self.is_unclosed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1846,6 +1889,9 @@ bitflags! {
|
||||
|
||||
/// The bytestring was deemed invalid by the parser.
|
||||
const INVALID = 1 << 4;
|
||||
|
||||
/// The byte string misses the matching closing quote(s).
|
||||
const UNCLOSED = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1897,6 +1943,12 @@ impl BytesLiteralFlags {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unclosed(mut self, unclosed: bool) -> Self {
|
||||
self.0.set(BytesLiteralFlagsInner::UNCLOSED, unclosed);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_prefix(mut self, prefix: ByteStringPrefix) -> Self {
|
||||
match prefix {
|
||||
@@ -1959,6 +2011,10 @@ impl StringFlags for BytesLiteralFlags {
|
||||
fn prefix(self) -> AnyStringPrefix {
|
||||
AnyStringPrefix::Bytes(self.prefix())
|
||||
}
|
||||
|
||||
fn is_unclosed(self) -> bool {
|
||||
self.0.intersects(BytesLiteralFlagsInner::UNCLOSED)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BytesLiteralFlags {
|
||||
@@ -1967,6 +2023,7 @@ impl fmt::Debug for BytesLiteralFlags {
|
||||
.field("quote_style", &self.quote_style())
|
||||
.field("prefix", &self.prefix())
|
||||
.field("triple_quoted", &self.is_triple_quoted())
|
||||
.field("unclosed", &self.is_unclosed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -2028,7 +2085,7 @@ bitflags! {
|
||||
/// prefix flags is by calling the `as_flags()` method on the
|
||||
/// `StringPrefix` enum.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct AnyStringFlagsInner: u8 {
|
||||
struct AnyStringFlagsInner: u16 {
|
||||
/// The string uses double quotes (`"`).
|
||||
/// If this flag is not set, the string uses single quotes (`'`).
|
||||
const DOUBLE = 1 << 0;
|
||||
@@ -2071,6 +2128,9 @@ bitflags! {
|
||||
/// for why we track the casing of the `r` prefix,
|
||||
/// but not for any other prefix
|
||||
const R_PREFIX_UPPER = 1 << 7;
|
||||
|
||||
/// String without matching closing quote(s).
|
||||
const UNCLOSED = 1 << 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2166,6 +2226,12 @@ impl AnyStringFlags {
|
||||
.set(AnyStringFlagsInner::TRIPLE_QUOTED, triple_quotes.is_yes());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unclosed(mut self, unclosed: bool) -> Self {
|
||||
self.0.set(AnyStringFlagsInner::UNCLOSED, unclosed);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl StringFlags for AnyStringFlags {
|
||||
@@ -2234,6 +2300,10 @@ impl StringFlags for AnyStringFlags {
|
||||
}
|
||||
AnyStringPrefix::Regular(StringLiteralPrefix::Empty)
|
||||
}
|
||||
|
||||
fn is_unclosed(self) -> bool {
|
||||
self.0.intersects(AnyStringFlagsInner::UNCLOSED)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnyStringFlags {
|
||||
@@ -2242,6 +2312,7 @@ impl fmt::Debug for AnyStringFlags {
|
||||
.field("prefix", &self.prefix())
|
||||
.field("triple_quoted", &self.is_triple_quoted())
|
||||
.field("quote_style", &self.quote_style())
|
||||
.field("unclosed", &self.is_unclosed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -2258,6 +2329,7 @@ impl From<AnyStringFlags> for StringLiteralFlags {
|
||||
.with_quote_style(value.quote_style())
|
||||
.with_prefix(prefix)
|
||||
.with_triple_quotes(value.triple_quotes())
|
||||
.with_unclosed(value.is_unclosed())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2279,6 +2351,7 @@ impl From<AnyStringFlags> for BytesLiteralFlags {
|
||||
.with_quote_style(value.quote_style())
|
||||
.with_prefix(bytestring_prefix)
|
||||
.with_triple_quotes(value.triple_quotes())
|
||||
.with_unclosed(value.is_unclosed())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2300,6 +2373,7 @@ impl From<AnyStringFlags> for FStringFlags {
|
||||
.with_quote_style(value.quote_style())
|
||||
.with_prefix(prefix)
|
||||
.with_triple_quotes(value.triple_quotes())
|
||||
.with_unclosed(value.is_unclosed())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2321,6 +2395,7 @@ impl From<AnyStringFlags> for TStringFlags {
|
||||
.with_quote_style(value.quote_style())
|
||||
.with_prefix(prefix)
|
||||
.with_triple_quotes(value.triple_quotes())
|
||||
.with_unclosed(value.is_unclosed())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
crates/ruff_python_formatter/resources/test/fixtures/black/cases/annotations.py
vendored
Normal file
7
crates/ruff_python_formatter/resources/test/fixtures/black/cases/annotations.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# regression test for #1765
|
||||
class Foo:
|
||||
def foo(self):
|
||||
if True:
|
||||
content_ids: Mapping[
|
||||
str, Optional[ContentId]
|
||||
] = self.publisher_content_store.store_config_contents(files)
|
||||
7
crates/ruff_python_formatter/resources/test/fixtures/black/cases/annotations.py.expect
vendored
Normal file
7
crates/ruff_python_formatter/resources/test/fixtures/black/cases/annotations.py.expect
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# regression test for #1765
|
||||
class Foo:
|
||||
def foo(self):
|
||||
if True:
|
||||
content_ids: Mapping[str, Optional[ContentId]] = (
|
||||
self.publisher_content_store.store_config_contents(files)
|
||||
)
|
||||
@@ -1 +1 @@
|
||||
{"target_version": "3.10"}
|
||||
{"target_version": "3.10"}
|
||||
35
crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py
vendored
Normal file
35
crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# long variable name
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 0
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 1 # with a comment
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = [
|
||||
1, 2, 3
|
||||
]
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function()
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
|
||||
arg1, arg2, arg3
|
||||
)
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
|
||||
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
|
||||
)
|
||||
# long function name
|
||||
normal_name = but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying()
|
||||
normal_name = but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying(
|
||||
arg1, arg2, arg3
|
||||
)
|
||||
normal_name = but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying(
|
||||
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
|
||||
)
|
||||
string_variable_name = (
|
||||
"a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa
|
||||
)
|
||||
for key in """
|
||||
hostname
|
||||
port
|
||||
username
|
||||
""".split():
|
||||
if key in self.connect_kwargs:
|
||||
raise ValueError(err.format(key))
|
||||
concatenated_strings = "some strings that are " "concatenated implicitly, so if you put them on separate " "lines it will fit"
|
||||
del concatenated_strings, string_variable_name, normal_function_name, normal_name, need_more_to_make_the_line_long_enough
|
||||
del ([], name_1, name_2), [(), [], name_4, name_3], name_1[[name_2 for name_1 in name_0]]
|
||||
del (),
|
||||
61
crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py.expect
vendored
Normal file
61
crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py.expect
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# long variable name
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
|
||||
0
|
||||
)
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
|
||||
1 # with a comment
|
||||
)
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
|
||||
function()
|
||||
)
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
|
||||
arg1, arg2, arg3
|
||||
)
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
|
||||
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
|
||||
)
|
||||
# long function name
|
||||
normal_name = (
|
||||
but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying()
|
||||
)
|
||||
normal_name = (
|
||||
but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying(
|
||||
arg1, arg2, arg3
|
||||
)
|
||||
)
|
||||
normal_name = (
|
||||
but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying(
|
||||
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
|
||||
)
|
||||
)
|
||||
string_variable_name = "a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa
|
||||
for key in """
|
||||
hostname
|
||||
port
|
||||
username
|
||||
""".split():
|
||||
if key in self.connect_kwargs:
|
||||
raise ValueError(err.format(key))
|
||||
concatenated_strings = (
|
||||
"some strings that are "
|
||||
"concatenated implicitly, so if you put them on separate "
|
||||
"lines it will fit"
|
||||
)
|
||||
del (
|
||||
concatenated_strings,
|
||||
string_variable_name,
|
||||
normal_function_name,
|
||||
normal_name,
|
||||
need_more_to_make_the_line_long_enough,
|
||||
)
|
||||
del (
|
||||
([], name_1, name_2),
|
||||
[(), [], name_4, name_3],
|
||||
name_1[[name_2 for name_1 in name_0]],
|
||||
)
|
||||
del ((),)
|
||||
@@ -1 +1 @@
|
||||
{"target_version": "3.8"}
|
||||
{"target_version": "3.8"}
|
||||
@@ -82,3 +82,9 @@ async def func():
|
||||
argument1, argument2, argument3="some_value"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
with (x, y) as z:
|
||||
pass
|
||||
|
||||
@@ -83,3 +83,8 @@ async def func():
|
||||
some_other_function(argument1, argument2, argument3="some_value"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
with (x, y) as z:
|
||||
pass
|
||||
|
||||
3
crates/ruff_python_formatter/resources/test/fixtures/black/cases/docstring_newline.py
vendored
Normal file
3
crates/ruff_python_formatter/resources/test/fixtures/black/cases/docstring_newline.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
87 characters ............................................................................
|
||||
"""
|
||||
3
crates/ruff_python_formatter/resources/test/fixtures/black/cases/docstring_newline.py.expect
vendored
Normal file
3
crates/ruff_python_formatter/resources/test/fixtures/black/cases/docstring_newline.py.expect
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
87 characters ............................................................................
|
||||
"""
|
||||
8
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip10.py
vendored
Normal file
8
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip10.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
def foo(): return "mock" # fmt: skip
|
||||
if True: print("yay") # fmt: skip
|
||||
for i in range(10): print(i) # fmt: skip
|
||||
|
||||
j = 1 # fmt: skip
|
||||
while j < 10: j += 1 # fmt: skip
|
||||
|
||||
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
||||
8
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip10.py.expect
vendored
Normal file
8
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip10.py.expect
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
def foo(): return "mock" # fmt: skip
|
||||
if True: print("yay") # fmt: skip
|
||||
for i in range(10): print(i) # fmt: skip
|
||||
|
||||
j = 1 # fmt: skip
|
||||
while j < 10: j += 1 # fmt: skip
|
||||
|
||||
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
||||
6
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip11.py
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip11.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
# comment 1 # fmt: skip
|
||||
# comment 2
|
||||
6
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip11.py.expect
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip11.py.expect
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
# comment 1 # fmt: skip
|
||||
# comment 2
|
||||
15
crates/ruff_python_formatter/resources/test/fixtures/black/cases/format_unicode_escape_seq.py
vendored
Normal file
15
crates/ruff_python_formatter/resources/test/fixtures/black/cases/format_unicode_escape_seq.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
x = "\x1F"
|
||||
x = "\\x1B"
|
||||
x = "\\\x1B"
|
||||
x = "\U0001F60E"
|
||||
x = "\u0001F60E"
|
||||
x = r"\u0001F60E"
|
||||
x = "don't format me"
|
||||
x = "\xA3"
|
||||
x = "\u2717"
|
||||
x = "\uFaCe"
|
||||
x = "\N{ox}\N{OX}"
|
||||
x = "\N{lAtIn smaLL letteR x}"
|
||||
x = "\N{CYRILLIC small LETTER BYELORUSSIAN-UKRAINIAN I}"
|
||||
x = b"\x1Fdon't byte"
|
||||
x = rb"\x1Fdon't format"
|
||||
@@ -0,0 +1,15 @@
|
||||
x = "\x1f"
|
||||
x = "\\x1B"
|
||||
x = "\\\x1b"
|
||||
x = "\U0001f60e"
|
||||
x = "\u0001F60E"
|
||||
x = r"\u0001F60E"
|
||||
x = "don't format me"
|
||||
x = "\xa3"
|
||||
x = "\u2717"
|
||||
x = "\uface"
|
||||
x = "\N{OX}\N{OX}"
|
||||
x = "\N{LATIN SMALL LETTER X}"
|
||||
x = "\N{CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I}"
|
||||
x = b"\x1fdon't byte"
|
||||
x = rb"\x1Fdon't format"
|
||||
@@ -1 +0,0 @@
|
||||
{"target_version": "3.12"}
|
||||
34
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fstring_quotations.py
vendored
Normal file
34
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fstring_quotations.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Regression tests for long f-strings, including examples from issue #3623
|
||||
|
||||
a = (
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
)
|
||||
|
||||
a = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + \
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
|
||||
a = f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"' + \
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
|
||||
a = (
|
||||
f'bbbbbbb"{"b"}"'
|
||||
'aaaaaaaa'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'\"{"b"}\"'
|
||||
)
|
||||
|
||||
a = (
|
||||
r'\"{"b"}\"'
|
||||
)
|
||||
29
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fstring_quotations.py.expect
vendored
Normal file
29
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fstring_quotations.py.expect
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Regression tests for long f-strings, including examples from issue #3623
|
||||
|
||||
a = (
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
)
|
||||
|
||||
a = (
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
+ f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
+ f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = f'bbbbbbb"{"b"}"' "aaaaaaaa"
|
||||
|
||||
a = f'"{"b"}"'
|
||||
|
||||
a = f'"{"b"}"'
|
||||
|
||||
a = r'\"{"b"}\"'
|
||||
@@ -1 +1 @@
|
||||
{"preview": "enabled", "target_version": "3.10"}
|
||||
{"target_version": "3.10"}
|
||||
@@ -0,0 +1 @@
|
||||
{"target_version": "3.12"}
|
||||
133
crates/ruff_python_formatter/resources/test/fixtures/black/cases/generics_wrapping.py
vendored
Normal file
133
crates/ruff_python_formatter/resources/test/fixtures/black/cases/generics_wrapping.py
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic[T, B](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic[T, B,](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def both_magic[T, B,](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def plain_mixed2[T, B](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed2[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def something_something_function[
|
||||
T: Model
|
||||
](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[
|
||||
T
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def with_random_comments[
|
||||
Z
|
||||
# bye
|
||||
]():
|
||||
return a
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment
|
||||
U # comment
|
||||
,
|
||||
Z: # comment
|
||||
int
|
||||
](): pass
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment but it's long so it doesn't just move to the end of the line
|
||||
U # comment comment comm comm ent ent
|
||||
,
|
||||
Z: # comment ent ent comm comm comment
|
||||
int
|
||||
](): pass
|
||||
170
crates/ruff_python_formatter/resources/test/fixtures/black/cases/generics_wrapping.py.expect
vendored
Normal file
170
crates/ruff_python_formatter/resources/test/fixtures/black/cases/generics_wrapping.py.expect
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_multiline[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed1[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed2[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed2[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed2[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def something_something_function[T: Model](
|
||||
param: list[int], other_param: type[T], *, some_other_param: bool = True
|
||||
) -> QuerySet[T]:
|
||||
pass
|
||||
|
||||
|
||||
def func[
|
||||
A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere,
|
||||
LIKE_THIS,
|
||||
AND_THIS,
|
||||
ANOTHER_ONE,
|
||||
AND_YET_ANOTHER_ONE: ThisOneHasTyping,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
c: T,
|
||||
d: T,
|
||||
e: T,
|
||||
f: T,
|
||||
g: T,
|
||||
h: T,
|
||||
i: T,
|
||||
j: T,
|
||||
k: T,
|
||||
l: T,
|
||||
m: T,
|
||||
n: T,
|
||||
o: T,
|
||||
p: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def with_random_comments[
|
||||
Z
|
||||
# bye
|
||||
]():
|
||||
return a
|
||||
|
||||
|
||||
def func[T, U, Z: int](): # comment # comment # comment
|
||||
pass
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment but it's long so it doesn't just move to the end of the line
|
||||
U, # comment comment comm comm ent ent
|
||||
Z: int, # comment ent ent comm comm comment
|
||||
]():
|
||||
pass
|
||||
@@ -6,7 +6,7 @@ def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parame
|
||||
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
|
||||
# Adding some unformatted code covering a wide range of syntaxes.
|
||||
# Adding some unformated code covering a wide range of syntaxes.
|
||||
|
||||
if True:
|
||||
# Incorrectly indented prefix comments.
|
||||
|
||||
@@ -28,7 +28,7 @@ def foo3(
|
||||
|
||||
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
|
||||
# Adding some unformatted code covering a wide range of syntaxes.
|
||||
# Adding some unformated code covering a wide range of syntaxes.
|
||||
|
||||
if True:
|
||||
# Incorrectly indented prefix comments.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# flags: --line-ranges=6-7
|
||||
class Foo:
|
||||
|
||||
@overload
|
||||
def foo(): ...
|
||||
|
||||
def fox(self):
|
||||
print()
|
||||
@@ -0,0 +1,8 @@
|
||||
# flags: --line-ranges=6-7
|
||||
class Foo:
|
||||
|
||||
@overload
|
||||
def foo(): ...
|
||||
|
||||
def fox(self):
|
||||
print()
|
||||
@@ -0,0 +1,28 @@
|
||||
def func(
|
||||
arg1,
|
||||
arg2,
|
||||
) -> Set["this_is_a_very_long_module_name.AndAVeryLongClasName"
|
||||
".WithAVeryVeryVeryVeryVeryLongSubClassName"]:
|
||||
pass
|
||||
|
||||
|
||||
def func(
|
||||
argument: (
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[int] |"
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
),
|
||||
) -> (
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[int] |"
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func(
|
||||
argument: (
|
||||
"int |"
|
||||
"str"
|
||||
),
|
||||
) -> Set["int |"
|
||||
" str"]:
|
||||
pass
|
||||
@@ -0,0 +1,26 @@
|
||||
def func(
|
||||
arg1,
|
||||
arg2,
|
||||
) -> Set[
|
||||
"this_is_a_very_long_module_name.AndAVeryLongClasName"
|
||||
".WithAVeryVeryVeryVeryVeryLongSubClassName"
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def func(
|
||||
argument: (
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[int] |"
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
),
|
||||
) -> (
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[int] |"
|
||||
"VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func(
|
||||
argument: "int |" "str",
|
||||
) -> Set["int |" " str"]:
|
||||
pass
|
||||
@@ -1,6 +1,6 @@
|
||||
"""I am a very helpful module docstring.
|
||||
|
||||
With trailing spaces (only removed with unify_docstring_detection on):
|
||||
With trailing spaces:
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""I am a very helpful module docstring.
|
||||
|
||||
With trailing spaces (only removed with unify_docstring_detection on):
|
||||
With trailing spaces:
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/python
|
||||
|
||||
# regression test for #4762
|
||||
"""
|
||||
docstring
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/python
|
||||
|
||||
# regression test for #4762
|
||||
"""
|
||||
docstring
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
@@ -25,5 +25,4 @@ class MultilineDocstringsAsWell:
|
||||
|
||||
|
||||
class SingleQuotedDocstring:
|
||||
|
||||
"I'm a docstring but I don't even get triple quotes."
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"preview": "enabled", "target_version": "3.10"}
|
||||
{"target_version": "3.10"}
|
||||
@@ -19,7 +19,7 @@ z: (Short
|
||||
z: (int) = 2.3
|
||||
z: ((int)) = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
|
||||
@@ -28,7 +28,7 @@ z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"target_version": "3.11"}
|
||||
@@ -0,0 +1,5 @@
|
||||
def fn(*args: *tuple[*A, B]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
fn.__annotations__
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user