Compare commits
49 Commits
dcreager/i
...
david/no-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3f8f60e1e | ||
|
|
be94796e6d | ||
|
|
d2f73a404a | ||
|
|
e1cada1ec3 | ||
|
|
69ce064569 | ||
|
|
523fc418ed | ||
|
|
24d0f65d62 | ||
|
|
a802d7a0ea | ||
|
|
692b7d7f0c | ||
|
|
d363d49ab7 | ||
|
|
281ae8eb34 | ||
|
|
d61902c89f | ||
|
|
511710e1ef | ||
|
|
da04d92ff2 | ||
|
|
0f21567405 | ||
|
|
be32f8acb1 | ||
|
|
1197035c02 | ||
|
|
a7c38eb122 | ||
|
|
0520d11a66 | ||
|
|
54cd9d889d | ||
|
|
44e678a222 | ||
|
|
38c074e67d | ||
|
|
c2ae9c7806 | ||
|
|
5a1201b868 | ||
|
|
48b50128eb | ||
|
|
991e8ed178 | ||
|
|
0fc5520404 | ||
|
|
b82404f706 | ||
|
|
4622b8f849 | ||
|
|
bb3a88cdd5 | ||
|
|
029a7aa200 | ||
|
|
590ce9d9ed | ||
|
|
0b8de723c6 | ||
|
|
36d4b02fa9 | ||
|
|
1f8297cfe6 | ||
|
|
b6b96d75eb | ||
|
|
3c229ae58a | ||
|
|
44d9063058 | ||
|
|
09306a1d69 | ||
|
|
68c1fa86c8 | ||
|
|
16efe53a72 | ||
|
|
7532155c9b | ||
|
|
7198e53182 | ||
|
|
8ca2b5555d | ||
|
|
6d2cf3475f | ||
|
|
e4384fc212 | ||
|
|
6e7ff07065 | ||
|
|
c7e2bfd759 | ||
|
|
c424007645 |
@@ -8,7 +8,3 @@ benchmark = "bench -p ruff_benchmark --bench linter --bench formatter --"
|
||||
# See: https://github.com/astral-sh/ruff/issues/11503
|
||||
[target.'cfg(all(target_env="msvc", target_os = "windows"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.'wasm32-unknown-unknown']
|
||||
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
|
||||
rustflags = ["--cfg", 'getrandom_backend="wasm_js"']
|
||||
85
.github/workflows/ci.yaml
vendored
85
.github/workflows/ci.yaml
vendored
@@ -22,7 +22,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.13"
|
||||
PYTHON_VERSION: "3.14"
|
||||
NEXTEST_PROFILE: ci
|
||||
|
||||
jobs:
|
||||
@@ -277,7 +277,8 @@ jobs:
|
||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
# Dogfood ty on py-fuzzer
|
||||
- run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
@@ -333,9 +334,14 @@ jobs:
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
cargo-test-windows:
|
||||
name: "cargo test (windows)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
|
||||
cargo-test-other:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
|
||||
- macos-latest
|
||||
name: "cargo test (${{ matrix.platform }})"
|
||||
runs-on: ${{ matrix.platform }}
|
||||
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
|
||||
@@ -354,37 +360,6 @@ jobs:
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
env:
|
||||
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
run: |
|
||||
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"
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
@@ -498,9 +473,10 @@ jobs:
|
||||
chmod +x "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
(
|
||||
uvx \
|
||||
uv run \
|
||||
--python="${PYTHON_VERSION}" \
|
||||
--from=./python/py-fuzzer \
|
||||
--project=./python/py-fuzzer \
|
||||
--locked \
|
||||
fuzz \
|
||||
--test-executable="${DOWNLOAD_PATH}/ruff" \
|
||||
--bin=ruff \
|
||||
@@ -518,6 +494,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -532,6 +509,11 @@ jobs:
|
||||
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
|
||||
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
|
||||
- run: cargo check
|
||||
# Lint/format/type-check py-fuzzer
|
||||
# (dogfooding with ty is done in a separate job)
|
||||
- run: uv run --directory=./python/py-fuzzer mypy
|
||||
- run: uv run --directory=./python/py-fuzzer ruff format --check
|
||||
- run: uv run --directory=./python/py-fuzzer ruff check
|
||||
|
||||
ecosystem:
|
||||
name: "ecosystem"
|
||||
@@ -547,9 +529,11 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
# TODO: figure out why `ruff-ecosystem` crashes on Python 3.14
|
||||
python-version: "3.13"
|
||||
activate-environment: true
|
||||
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
name: Download comparison Ruff binary
|
||||
@@ -568,7 +552,7 @@ jobs:
|
||||
|
||||
- name: Install ruff-ecosystem
|
||||
run: |
|
||||
pip install ./python/ruff-ecosystem
|
||||
uv pip install ./python/ruff-ecosystem
|
||||
|
||||
- name: Run `ruff check` stable ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||
@@ -666,7 +650,7 @@ jobs:
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 5 || 20 }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -694,15 +678,16 @@ jobs:
|
||||
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
|
||||
|
||||
(
|
||||
uvx \
|
||||
uv run \
|
||||
--python="${PYTHON_VERSION}" \
|
||||
--from=./python/py-fuzzer \
|
||||
--project=./python/py-fuzzer \
|
||||
--locked \
|
||||
fuzz \
|
||||
--test-executable="${NEW_TY}/ty" \
|
||||
--baseline-executable="${PWD}/ty" \
|
||||
--only-new-bugs \
|
||||
--bin=ty \
|
||||
0-500
|
||||
0-1000
|
||||
)
|
||||
|
||||
cargo-shear:
|
||||
@@ -803,9 +788,6 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -816,12 +798,15 @@ jobs:
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
activate-environment: true
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
run: uv pip install -r docs/requirements-insiders.txt
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: uv pip install -r docs/requirements.txt --system
|
||||
run: uv pip install -r docs/requirements.txt
|
||||
- name: "Update README File"
|
||||
run: python scripts/transform_readme.py --target mkdocs
|
||||
- name: "Generate docs"
|
||||
|
||||
7
.github/workflows/daily_fuzz.yaml
vendored
7
.github/workflows/daily_fuzz.yaml
vendored
@@ -48,9 +48,10 @@ jobs:
|
||||
run: |
|
||||
# shellcheck disable=SC2046
|
||||
(
|
||||
uvx \
|
||||
--python=3.12 \
|
||||
--from=./python/py-fuzzer \
|
||||
uv run \
|
||||
--python=3.14 \
|
||||
--project=./python/py-fuzzer \
|
||||
--locked \
|
||||
fuzz \
|
||||
--test-executable=target/debug/ruff \
|
||||
--bin=ruff \
|
||||
|
||||
@@ -35,6 +35,7 @@ repos:
|
||||
rev: 0.7.22
|
||||
hooks:
|
||||
- id: mdformat
|
||||
language: python # means renovate will also update `additional_dependencies`
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs==4.0.0
|
||||
- mdformat-footnote==0.1.1
|
||||
@@ -58,6 +59,7 @@ repos:
|
||||
rev: 1.19.1
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
language: python # means renovate will also update `additional_dependencies`
|
||||
args: ["--pyi", "--line-length", "130"]
|
||||
files: '^crates/.*/resources/mdtest/.*\.md'
|
||||
exclude: |
|
||||
|
||||
124
Cargo.lock
generated
124
Cargo.lock
generated
@@ -433,9 +433,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -443,9 +443,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -486,9 +486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.47"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -633,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -642,7 +642,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1007,7 +1007,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1093,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1262,20 +1262,20 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1287,9 +1287,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
@@ -1690,7 +1690,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1754,7 +1754,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1789,7 +1789,7 @@ version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -2072,7 +2072,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2627,9 +2627,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2724,7 +2724,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2767,6 +2767,26 @@ dependencies = [
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.3"
|
||||
@@ -2781,9 +2801,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.11"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3197,6 +3217,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
|
||||
@@ -3430,7 +3451,7 @@ version = "0.14.1"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_formatter",
|
||||
@@ -3484,6 +3505,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
@@ -3523,7 +3545,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3589,11 +3611,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3601,9 +3624,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3625,9 +3648,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@@ -3646,18 +3669,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3795,9 +3818,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
version = "0.6.21"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
|
||||
checksum = "805d09a74586d9b17061e5be6ee5f8cc37e5982c349948114ffc5f68093fe5ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -3810,7 +3833,7 @@ dependencies = [
|
||||
"similar",
|
||||
"snapbox-macros",
|
||||
"wait-timeout",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3915,10 +3938,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4384,6 +4407,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.16",
|
||||
"toml",
|
||||
"tracing",
|
||||
@@ -4431,6 +4455,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strsim",
|
||||
@@ -4539,7 +4564,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_db",
|
||||
@@ -4732,7 +4757,7 @@ version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"rand 0.9.2",
|
||||
"uuid-macro-internal",
|
||||
@@ -4844,15 +4869,6 @@ version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
@@ -5004,7 +5020,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -152,7 +152,7 @@ salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef9f9329be6923ac
|
||||
"salsa_unstable",
|
||||
"inventory",
|
||||
] }
|
||||
schemars = { version = "0.8.16" }
|
||||
schemars = { version = "1.0.4" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.4" }
|
||||
|
||||
@@ -89,8 +89,7 @@ creator of [isort](https://github.com/PyCQA/isort):
|
||||
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
|
||||
> it was working till I intentionally introduced some errors.
|
||||
|
||||
[**Tim Abbott**](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028), lead
|
||||
developer of [Zulip](https://github.com/zulip/zulip):
|
||||
[**Tim Abbott**](https://github.com/zulip/zulip/pull/23431#issuecomment-1302557034), lead developer of [Zulip](https://github.com/zulip/zulip) (also [here](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028)):
|
||||
|
||||
> This is just ridiculously fast... `ruff` is amazing.
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use serde::{Serialize, Serializer};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_linter::FixAvailability;
|
||||
use ruff_linter::codes::RuleGroup;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
|
||||
use crate::args::HelpFormat;
|
||||
@@ -19,9 +20,11 @@ struct Explanation<'a> {
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
fix: String,
|
||||
fix_availability: FixAvailability,
|
||||
#[expect(clippy::struct_field_names)]
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
status: RuleGroup,
|
||||
}
|
||||
|
||||
impl<'a> Explanation<'a> {
|
||||
@@ -36,8 +39,10 @@ impl<'a> Explanation<'a> {
|
||||
summary: rule.message_formats()[0],
|
||||
message_formats: rule.message_formats(),
|
||||
fix,
|
||||
fix_availability: rule.fixable(),
|
||||
explanation: rule.explanation(),
|
||||
preview: rule.is_preview(),
|
||||
status: rule.group(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ use std::{
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
mod format;
|
||||
mod lint;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
@@ -57,6 +58,16 @@ impl CliTest {
|
||||
Self::with_settings(|_, settings| settings)
|
||||
}
|
||||
|
||||
pub(crate) fn with_files<'a>(
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let case = Self::new()?;
|
||||
for file in files {
|
||||
case.write_file(file.0, file.1)?;
|
||||
}
|
||||
Ok(case)
|
||||
}
|
||||
|
||||
pub(crate) fn with_settings(
|
||||
setup_settings: impl FnOnce(&Path, insta::Settings) -> insta::Settings,
|
||||
) -> Result<Self> {
|
||||
@@ -174,4 +185,10 @@ impl CliTest {
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
pub(crate) fn format_command(&self) -> Command {
|
||||
let mut command = self.command();
|
||||
command.args(["format", "--no-cache"]);
|
||||
command
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -7,6 +7,7 @@ info:
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- grouped
|
||||
- "--preview"
|
||||
- "--check"
|
||||
- input.py
|
||||
---
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -7,6 +7,7 @@ info:
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- pylint
|
||||
- "--preview"
|
||||
- "--check"
|
||||
- input.py
|
||||
---
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/format.rs
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -951,6 +951,16 @@ fn rule_f401() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_f401_output_json() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_f401_output_text() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "text"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_invalid_rule_name() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r"
|
||||
@@ -965,6 +975,34 @@ fn rule_invalid_rule_name() {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_invalid_rule_name_output_json() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'RUF404' for '[RULE]'
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_invalid_rule_name_output_text() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'RUF404' for '[RULE]'
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_statistics() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff/tests/integration_test.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- rule
|
||||
- F401
|
||||
- "--output-format"
|
||||
- json
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"name": "unused-import",
|
||||
"code": "F401",
|
||||
"linter": "Pyflakes",
|
||||
"summary": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
|
||||
"message_formats": [
|
||||
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
|
||||
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias",
|
||||
"`{name}` imported but unused"
|
||||
],
|
||||
"fix": "Fix is sometimes available.",
|
||||
"fix_availability": "Sometimes",
|
||||
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
|
||||
"preview": false,
|
||||
"status": "Stable"
|
||||
}
|
||||
----- stderr -----
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
source: crates/ruff/tests/integration_test.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- rule
|
||||
- F401
|
||||
- "--output-format"
|
||||
- text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# unused-import (F401)
|
||||
|
||||
Derived from the **Pyflakes** linter.
|
||||
|
||||
Fix is sometimes available.
|
||||
|
||||
## What it does
|
||||
Checks for unused imports.
|
||||
|
||||
## Why is this bad?
|
||||
Unused imports add a performance overhead at runtime, and risk creating
|
||||
import cycles. They also increase the cognitive load of reading the code.
|
||||
|
||||
If an import statement is used to check for the availability or existence
|
||||
of a module, consider using `importlib.util.find_spec` instead.
|
||||
|
||||
If an import statement is used to re-export a symbol as part of a module's
|
||||
public interface, consider using a "redundant" import alias, which
|
||||
instructs Ruff (and other tools) to respect the re-export, and avoid
|
||||
marking it as unused, as in:
|
||||
|
||||
```python
|
||||
from module import member as member
|
||||
```
|
||||
|
||||
Alternatively, you can use `__all__` to declare a symbol as part of the module's
|
||||
interface, as in:
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
import some_module
|
||||
|
||||
__all__ = ["some_module"]
|
||||
```
|
||||
|
||||
## Preview
|
||||
When [preview] is enabled (and certain simplifying assumptions
|
||||
are met), we analyze all import statements for a given module
|
||||
when determining whether an import is used, rather than simply
|
||||
the last of these statements. This can result in both different and
|
||||
more import statements being marked as unused.
|
||||
|
||||
For example, if a module consists of
|
||||
|
||||
```python
|
||||
import a
|
||||
import a.b
|
||||
```
|
||||
|
||||
then both statements are marked as unused under [preview], whereas
|
||||
only the second is marked as unused under stable behavior.
|
||||
|
||||
As another example, if a module consists of
|
||||
|
||||
```python
|
||||
import a.b
|
||||
import a
|
||||
|
||||
a.b.foo()
|
||||
```
|
||||
|
||||
then a diagnostic will only be emitted for the first line under [preview],
|
||||
whereas a diagnostic would only be emitted for the second line under
|
||||
stable behavior.
|
||||
|
||||
Note that this behavior is somewhat subjective and is designed
|
||||
to conform to the developer's intuition rather than Python's actual
|
||||
execution. To wit, the statement `import a.b` automatically executes
|
||||
`import a`, so in some sense `import a` is _always_ redundant
|
||||
in the presence of `import a.b`.
|
||||
|
||||
|
||||
## Fix safety
|
||||
|
||||
Fixes to remove unused imports are safe, except in `__init__.py` files.
|
||||
|
||||
Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the
|
||||
type of the unused import. Ruff will suggest a safe fix to export first-party imports with
|
||||
either a redundant alias or, if already present in the file, an `__all__` entry. If multiple
|
||||
`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix
|
||||
to remove third-party and standard library imports -- the fix is unsafe because the module's
|
||||
interface changes.
|
||||
|
||||
See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)
|
||||
for more details on how Ruff
|
||||
determines whether an import is first or third-party.
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
import numpy as np # unused import
|
||||
|
||||
|
||||
def area(radius):
|
||||
return 3.14 * radius**2
|
||||
```
|
||||
|
||||
Use instead:
|
||||
|
||||
```python
|
||||
def area(radius):
|
||||
return 3.14 * radius**2
|
||||
```
|
||||
|
||||
To check the availability of a module, use `importlib.util.find_spec`:
|
||||
|
||||
```python
|
||||
from importlib.util import find_spec
|
||||
|
||||
if find_spec("numpy") is not None:
|
||||
print("numpy is installed")
|
||||
else:
|
||||
print("numpy is not installed")
|
||||
```
|
||||
|
||||
## Options
|
||||
- `lint.ignore-init-module-imports`
|
||||
- `lint.pyflakes.allowed-unused-imports`
|
||||
|
||||
## References
|
||||
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
||||
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
||||
- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)
|
||||
|
||||
[preview]: https://docs.astral.sh/ruff/preview/
|
||||
|
||||
----- stderr -----
|
||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use pretty_assertions::StrComparison;
|
||||
use schemars::schema_for;
|
||||
use schemars::generate::SchemaSettings;
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
@@ -17,7 +17,9 @@ pub(crate) struct Args {
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let schema = schema_for!(Options);
|
||||
let settings = SchemaSettings::draft07();
|
||||
let generator = settings.into_generator();
|
||||
let schema = generator.into_root_schema_for::<Options>();
|
||||
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let filename = "ruff.schema.json";
|
||||
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use pretty_assertions::StrComparison;
|
||||
use schemars::schema_for;
|
||||
use schemars::generate::SchemaSettings;
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
@@ -17,7 +17,9 @@ pub(crate) struct Args {
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let schema = schema_for!(Options);
|
||||
let settings = SchemaSettings::draft07();
|
||||
let generator = settings.into_generator();
|
||||
let schema = generator.into_root_schema_for::<Options>();
|
||||
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let filename = "ty.schema.json";
|
||||
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
87
crates/ruff_linter/resources/test/fixtures/fastapi/FAST002_2.py
vendored
Normal file
87
crates/ruff_linter/resources/test/fixtures/fastapi/FAST002_2.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Test FAST002 ellipsis handling."""
|
||||
|
||||
from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# Cases that should be fixed - ellipsis should be removed
|
||||
|
||||
|
||||
@app.get("/test1")
|
||||
async def test_ellipsis_query(
|
||||
# This should become: param: Annotated[str, Query(description="Test param")]
|
||||
param: str = Query(..., description="Test param"),
|
||||
) -> str:
|
||||
return param
|
||||
|
||||
|
||||
@app.get("/test2")
|
||||
async def test_ellipsis_header(
|
||||
# This should become: auth: Annotated[str, Header(description="Auth header")]
|
||||
auth: str = Header(..., description="Auth header"),
|
||||
) -> str:
|
||||
return auth
|
||||
|
||||
|
||||
@app.post("/test3")
|
||||
async def test_ellipsis_body(
|
||||
# This should become: data: Annotated[dict, Body(description="Request body")]
|
||||
data: dict = Body(..., description="Request body"),
|
||||
) -> dict:
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/test4")
|
||||
async def test_ellipsis_cookie(
|
||||
# This should become: session: Annotated[str, Cookie(description="Session ID")]
|
||||
session: str = Cookie(..., description="Session ID"),
|
||||
) -> str:
|
||||
return session
|
||||
|
||||
|
||||
@app.get("/test5")
|
||||
async def test_simple_ellipsis(
|
||||
# This should become: id: Annotated[str, Query()]
|
||||
id: str = Query(...),
|
||||
) -> str:
|
||||
return id
|
||||
|
||||
|
||||
@app.get("/test6")
|
||||
async def test_multiple_kwargs_with_ellipsis(
|
||||
# This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
|
||||
param: str = Query(..., description="Test", min_length=1, max_length=10),
|
||||
) -> str:
|
||||
return param
|
||||
|
||||
|
||||
# Cases with actual default values - these should preserve the default
|
||||
|
||||
|
||||
@app.get("/test7")
|
||||
async def test_with_default_value(
|
||||
# This should become: param: Annotated[str, Query(description="Test")] = "default"
|
||||
param: str = Query("default", description="Test"),
|
||||
) -> str:
|
||||
return param
|
||||
|
||||
|
||||
@app.get("/test8")
|
||||
async def test_with_default_none(
|
||||
# This should become: param: Annotated[str | None, Query(description="Test")] = None
|
||||
param: str | None = Query(None, description="Test"),
|
||||
) -> str:
|
||||
return param or "empty"
|
||||
|
||||
|
||||
@app.get("/test9")
|
||||
async def test_mixed_parameters(
|
||||
# First param should be fixed with default preserved
|
||||
optional_param: str = Query("default", description="Optional"),
|
||||
# Second param should not be fixed because of the preceding default
|
||||
required_param: str = Query(..., description="Required"),
|
||||
# Third param should be fixed with default preserved
|
||||
another_optional_param: int = Query(42, description="Another optional"),
|
||||
) -> str:
|
||||
return f"{required_param}-{optional_param}-{another_optional_param}"
|
||||
@@ -256,3 +256,12 @@ async def f4():
|
||||
@app.get("/f5/{param: int}")
|
||||
async def f5():
|
||||
return locals()
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20941
|
||||
@app.get("/imports/{import}")
|
||||
async def get_import():
|
||||
...
|
||||
|
||||
@app.get("/debug/{__debug__}")
|
||||
async def get_debug():
|
||||
...
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from itertools import count, cycle, repeat
|
||||
|
||||
# Errors
|
||||
zip()
|
||||
zip(range(3))
|
||||
zip("a", "b")
|
||||
zip("a", "b", *zip("c"))
|
||||
zip(zip("a"), strict=False)
|
||||
zip(zip("a", strict=True))
|
||||
zip(zip("a", "b"), strict=False)
|
||||
zip(zip("a", strict=True),"b")
|
||||
|
||||
# OK
|
||||
zip(range(3), strict=True)
|
||||
@@ -27,3 +25,10 @@ zip([1, 2, 3], repeat(1, times=4))
|
||||
import builtins
|
||||
# Still an error even though it uses the qualified name
|
||||
builtins.zip([1, 2, 3])
|
||||
|
||||
# Regression https://github.com/astral-sh/ruff/issues/20997
|
||||
# Ok
|
||||
zip()
|
||||
zip(range(3))
|
||||
# Error
|
||||
zip(*lot_of_iterators)
|
||||
|
||||
@@ -31,3 +31,6 @@ map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3]))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None))
|
||||
map(lambda x, y: x + y, [1, 2, 3], count())
|
||||
|
||||
# Regression https://github.com/astral-sh/ruff/issues/20997
|
||||
map(f, *lots_of_iterators)
|
||||
|
||||
@@ -2,3 +2,52 @@ _(f"{'value'}")
|
||||
|
||||
# Don't trigger for t-strings
|
||||
_(t"{'value'}")
|
||||
|
||||
gettext(f"{'value'}")
|
||||
ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
||||
_gettext(f"{'value'}") # no lint
|
||||
gettext_fn(f"{'value'}") # no lint
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028 - import aliases
|
||||
import gettext as gettext_mod
|
||||
from gettext import (
|
||||
gettext as gettext_fn,
|
||||
ngettext as ngettext_fn,
|
||||
)
|
||||
|
||||
name = "Guido"
|
||||
|
||||
gettext_mod.gettext(f"Hello, {name}!")
|
||||
gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
gettext_fn(f"Hello, {name}!")
|
||||
ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
|
||||
def _(message): return message
|
||||
|
||||
animals = [_(f"{'mollusk'}"),
|
||||
_(f"{'albatross'}"),
|
||||
_(f"{'rat'}"),
|
||||
_(f"{'penguin'}"),
|
||||
_(f"{'python'}"),]
|
||||
|
||||
del _
|
||||
|
||||
for a in animals:
|
||||
print(_(a))
|
||||
print(_(f"{a}"))
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028
|
||||
# - installed translations/i18n functions dynamically assigned to builtins
|
||||
import builtins
|
||||
|
||||
gettext_mod.install("domain")
|
||||
builtins.__dict__["gettext"] = gettext_fn
|
||||
|
||||
builtins._(f"{'value'}")
|
||||
builtins.gettext(f"{'value'}")
|
||||
builtins.ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
||||
@@ -1 +1,49 @@
|
||||
_("{}".format("line"))
|
||||
gettext("{}".format("line"))
|
||||
ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
||||
_gettext("{}".format("line")) # no lint
|
||||
gettext_fn("{}".format("line")) # no lint
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028
|
||||
import gettext as gettext_mod
|
||||
from gettext import (
|
||||
gettext as gettext_fn,
|
||||
ngettext as ngettext_fn,
|
||||
)
|
||||
|
||||
name = "Guido"
|
||||
|
||||
gettext_mod.gettext("Hello, {}!".format(name))
|
||||
gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
gettext_fn("Hello, {}!".format(name))
|
||||
ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
|
||||
def _(message): return message
|
||||
|
||||
animals = [_("{}".format("mollusk")),
|
||||
_("{}".format("albatross")),
|
||||
_("{}".format("rat")),
|
||||
_("{}".format("penguin")),
|
||||
_("{}".format("python")),]
|
||||
|
||||
del _
|
||||
|
||||
for a in animals:
|
||||
print(_(a))
|
||||
print(_("{}".format(a)))
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028
|
||||
# - installed translations/i18n functions dynamically assigned to builtins
|
||||
import builtins
|
||||
|
||||
gettext_mod.install("domain")
|
||||
builtins.__dict__["gettext"] = gettext_fn
|
||||
|
||||
builtins._("{}".format("line"))
|
||||
builtins.gettext("{}".format("line"))
|
||||
builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
||||
@@ -1 +1,49 @@
|
||||
_("%s" % "line")
|
||||
gettext("%s" % "line")
|
||||
ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
||||
_gettext("%s" % "line") # no lint
|
||||
gettext_fn("%s" % "line") # no lint
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028
|
||||
import gettext as gettext_mod
|
||||
from gettext import (
|
||||
gettext as gettext_fn,
|
||||
ngettext as ngettext_fn,
|
||||
)
|
||||
|
||||
name = "Guido"
|
||||
|
||||
gettext_mod.gettext("Hello, %s!" % name)
|
||||
gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
gettext_fn("Hello, %s!" % name)
|
||||
ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
|
||||
def _(message): return message
|
||||
|
||||
animals = [_("%s" %"mollusk"),
|
||||
_("%s" %"albatross"),
|
||||
_("%s" %"rat"),
|
||||
_("%s" %"penguin"),
|
||||
_("%s" %"python"),]
|
||||
|
||||
del _
|
||||
|
||||
for a in animals:
|
||||
print(_(a))
|
||||
print(_("%s" % a))
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19028
|
||||
# - installed translations/i18n functions dynamically assigned to builtins
|
||||
import builtins
|
||||
|
||||
gettext_mod.install("domain")
|
||||
builtins.__dict__["gettext"] = gettext_fn
|
||||
|
||||
builtins._("%s" % "line")
|
||||
builtins.gettext("%s" % "line")
|
||||
builtins.ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
||||
@@ -269,3 +269,10 @@ raise ValueError(
|
||||
# Not a valid type annotation but this test shouldn't result in a panic.
|
||||
# Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
x: "'{} + {}'.format(x, y)"
|
||||
|
||||
# Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
# Fix should parenthesize walrus
|
||||
if __name__ == "__main__":
|
||||
number = 0
|
||||
string = "{}".format(number := number + 1)
|
||||
print(string)
|
||||
|
||||
@@ -1034,6 +1034,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::FormatInGetTextFuncCall,
|
||||
Rule::PrintfInGetTextFuncCall,
|
||||
]) && flake8_gettext::is_gettext_func_call(
|
||||
checker,
|
||||
func,
|
||||
&checker.settings().flake8_gettext.functions_names,
|
||||
) {
|
||||
|
||||
@@ -723,6 +723,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::IrrefutableCasePattern(_)
|
||||
| SemanticSyntaxErrorKind::SingleStarredAssignment
|
||||
| SemanticSyntaxErrorKind::WriteToDebug(_)
|
||||
| SemanticSyntaxErrorKind::DifferentMatchPatternBindings
|
||||
| SemanticSyntaxErrorKind::InvalidExpression(..)
|
||||
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
||||
|
||||
@@ -130,6 +130,7 @@ pub(crate) fn check_noqa(
|
||||
let edit = delete_comment(directive.range(), locator);
|
||||
let mut diagnostic = context
|
||||
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
}
|
||||
@@ -226,6 +227,7 @@ pub(crate) fn check_noqa(
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use ruff_db::diagnostic::SecondaryCode;
|
||||
use serde::Serialize;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::registry::Linter;
|
||||
@@ -74,7 +75,7 @@ impl serde::Serialize for NoqaCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
pub enum RuleGroup {
|
||||
/// The rule is stable.
|
||||
Stable,
|
||||
|
||||
@@ -274,3 +274,8 @@ pub(crate) const fn is_fix_write_whole_file_enabled(settings: &LinterSettings) -
|
||||
pub(crate) const fn is_typing_extensions_str_alias_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19045
|
||||
pub(crate) const fn is_extended_i18n_function_matching_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -257,9 +257,8 @@ pub struct PreviewOptions {
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use itertools::Itertools;
|
||||
use schemars::_serde_json::Value;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator};
|
||||
use serde_json::Value;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::RuleSelector;
|
||||
@@ -267,64 +266,65 @@ mod schema {
|
||||
use crate::rule_selector::{Linter, RuleCodePrefix};
|
||||
|
||||
impl JsonSchema for RuleSelector {
|
||||
fn schema_name() -> String {
|
||||
"RuleSelector".to_string()
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("RuleSelector")
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selectors.
|
||||
"ALL".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
// Include some common redirect targets for those legacy selectors.
|
||||
"C9".to_string(),
|
||||
"T1".to_string(),
|
||||
"T2".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
RuleCodePrefix::iter()
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
format!("{prefix}{code}")
|
||||
})
|
||||
.chain(Linter::iter().filter_map(|l| {
|
||||
let prefix = l.common_prefix();
|
||||
(!prefix.is_empty()).then(|| prefix.to_string())
|
||||
})),
|
||||
)
|
||||
.filter(|p| {
|
||||
// Exclude any prefixes where all of the rules are removed
|
||||
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
|
||||
RuleSelector::parse_no_redirect(p)
|
||||
{
|
||||
!prefix.rules().all(|rule| rule.is_removed())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
let enum_values: Vec<String> = [
|
||||
// Include the non-standard "ALL" selectors.
|
||||
"ALL".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
// Include some common redirect targets for those legacy selectors.
|
||||
"C9".to_string(),
|
||||
"T1".to_string(),
|
||||
"T2".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
RuleCodePrefix::iter()
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
format!("{prefix}{code}")
|
||||
})
|
||||
.filter(|_rule| {
|
||||
// Filter out all test-only rules
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
#[expect(clippy::used_underscore_binding)]
|
||||
if _rule.starts_with("RUF9") || _rule == "PLW0101" {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.sorted()
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
),
|
||||
..SchemaObject::default()
|
||||
.chain(Linter::iter().filter_map(|l| {
|
||||
let prefix = l.common_prefix();
|
||||
(!prefix.is_empty()).then(|| prefix.to_string())
|
||||
})),
|
||||
)
|
||||
.filter(|p| {
|
||||
// Exclude any prefixes where all of the rules are removed
|
||||
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
|
||||
RuleSelector::parse_no_redirect(p)
|
||||
{
|
||||
!prefix.rules().all(|rule| rule.is_removed())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.filter(|_rule| {
|
||||
// Filter out all test-only rules
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
#[expect(clippy::used_underscore_binding)]
|
||||
if _rule.starts_with("RUF9") || _rule == "PLW0101" {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.sorted()
|
||||
.collect();
|
||||
|
||||
let mut schema = schemars::json_schema!({ "type": "string" });
|
||||
schema.ensure_object().insert(
|
||||
"enum".to_string(),
|
||||
Value::Array(enum_values.into_iter().map(Value::String).collect()),
|
||||
);
|
||||
|
||||
schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ mod tests {
|
||||
#[test_case(Rule::FastApiRedundantResponseModel, Path::new("FAST001.py"))]
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))]
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_2.py"))]
|
||||
#[test_case(Rule::FastApiUnusedPathParameter, Path::new("FAST003.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy());
|
||||
@@ -56,6 +57,7 @@ mod tests {
|
||||
// since `typing.Annotated` was added in Python 3.9
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))]
|
||||
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_2.py"))]
|
||||
fn rules_py38(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}_py38", rule_code.name(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -275,19 +275,42 @@ fn create_diagnostic(
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
seen_default = true;
|
||||
format!(
|
||||
"{parameter_name}: {binding}[{annotation}, {default_}({kwarg_list})] \
|
||||
= {default_value}",
|
||||
// Check if the default argument is ellipsis (...), which in FastAPI means "required"
|
||||
let is_default_argument_ellipsis = matches!(
|
||||
dependency_call.default_argument.value(),
|
||||
ast::Expr::EllipsisLiteral(_)
|
||||
);
|
||||
|
||||
if is_default_argument_ellipsis && seen_default {
|
||||
// For ellipsis after a parameter with default, can't remove the default
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if !is_default_argument_ellipsis {
|
||||
// For actual default values, mark that we've seen a default
|
||||
seen_default = true;
|
||||
}
|
||||
|
||||
let base_format = format!(
|
||||
"{parameter_name}: {binding}[{annotation}, {default_}({kwarg_list})]",
|
||||
parameter_name = parameter.name,
|
||||
annotation = checker.locator().slice(parameter.annotation.range()),
|
||||
default_ = checker
|
||||
.locator()
|
||||
.slice(map_callable(parameter.default).range()),
|
||||
default_value = checker
|
||||
);
|
||||
|
||||
if is_default_argument_ellipsis {
|
||||
// For ellipsis, don't add a default value since the parameter
|
||||
// should remain required after conversion to Annotated
|
||||
base_format
|
||||
} else {
|
||||
// For actual default values, preserve them
|
||||
let default_value = checker
|
||||
.locator()
|
||||
.slice(dependency_call.default_argument.value().range()),
|
||||
)
|
||||
.slice(dependency_call.default_argument.value().range());
|
||||
format!("{base_format} = {default_value}")
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if seen_default {
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -191,7 +192,7 @@ pub(crate) fn fastapi_unused_path_parameter(
|
||||
.add_start(TextSize::from(range.start as u32 + 1))
|
||||
.sub_end(TextSize::from((path.len() - range.end + 1) as u32)),
|
||||
);
|
||||
if !is_positional {
|
||||
if !is_positional && is_identifier(path_param) && path_param != "__debug__" {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(add_parameter(
|
||||
path_param,
|
||||
&function_def.parameters,
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/fastapi/mod.rs
|
||||
---
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:14:5
|
||||
|
|
||||
12 | async def test_ellipsis_query(
|
||||
13 | # This should become: param: Annotated[str, Query(description="Test param")]
|
||||
14 | param: str = Query(..., description="Test param"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
15 | ) -> str:
|
||||
16 | return param
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
12 | @app.get("/test1")
|
||||
13 | async def test_ellipsis_query(
|
||||
14 | # This should become: param: Annotated[str, Query(description="Test param")]
|
||||
- param: str = Query(..., description="Test param"),
|
||||
15 + param: Annotated[str, Query(description="Test param")],
|
||||
16 | ) -> str:
|
||||
17 | return param
|
||||
18 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:22:5
|
||||
|
|
||||
20 | async def test_ellipsis_header(
|
||||
21 | # This should become: auth: Annotated[str, Header(description="Auth header")]
|
||||
22 | auth: str = Header(..., description="Auth header"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
23 | ) -> str:
|
||||
24 | return auth
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
20 | @app.get("/test2")
|
||||
21 | async def test_ellipsis_header(
|
||||
22 | # This should become: auth: Annotated[str, Header(description="Auth header")]
|
||||
- auth: str = Header(..., description="Auth header"),
|
||||
23 + auth: Annotated[str, Header(description="Auth header")],
|
||||
24 | ) -> str:
|
||||
25 | return auth
|
||||
26 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:30:5
|
||||
|
|
||||
28 | async def test_ellipsis_body(
|
||||
29 | # This should become: data: Annotated[dict, Body(description="Request body")]
|
||||
30 | data: dict = Body(..., description="Request body"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 | ) -> dict:
|
||||
32 | return data
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
28 | @app.post("/test3")
|
||||
29 | async def test_ellipsis_body(
|
||||
30 | # This should become: data: Annotated[dict, Body(description="Request body")]
|
||||
- data: dict = Body(..., description="Request body"),
|
||||
31 + data: Annotated[dict, Body(description="Request body")],
|
||||
32 | ) -> dict:
|
||||
33 | return data
|
||||
34 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:38:5
|
||||
|
|
||||
36 | async def test_ellipsis_cookie(
|
||||
37 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
|
||||
38 | session: str = Cookie(..., description="Session ID"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
39 | ) -> str:
|
||||
40 | return session
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
36 | @app.get("/test4")
|
||||
37 | async def test_ellipsis_cookie(
|
||||
38 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
|
||||
- session: str = Cookie(..., description="Session ID"),
|
||||
39 + session: Annotated[str, Cookie(description="Session ID")],
|
||||
40 | ) -> str:
|
||||
41 | return session
|
||||
42 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:46:5
|
||||
|
|
||||
44 | async def test_simple_ellipsis(
|
||||
45 | # This should become: id: Annotated[str, Query()]
|
||||
46 | id: str = Query(...),
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
47 | ) -> str:
|
||||
48 | return id
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
44 | @app.get("/test5")
|
||||
45 | async def test_simple_ellipsis(
|
||||
46 | # This should become: id: Annotated[str, Query()]
|
||||
- id: str = Query(...),
|
||||
47 + id: Annotated[str, Query()],
|
||||
48 | ) -> str:
|
||||
49 | return id
|
||||
50 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:54:5
|
||||
|
|
||||
52 | async def test_multiple_kwargs_with_ellipsis(
|
||||
53 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
|
||||
54 | param: str = Query(..., description="Test", min_length=1, max_length=10),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
55 | ) -> str:
|
||||
56 | return param
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
52 | @app.get("/test6")
|
||||
53 | async def test_multiple_kwargs_with_ellipsis(
|
||||
54 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
|
||||
- param: str = Query(..., description="Test", min_length=1, max_length=10),
|
||||
55 + param: Annotated[str, Query(description="Test", min_length=1, max_length=10)],
|
||||
56 | ) -> str:
|
||||
57 | return param
|
||||
58 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:65:5
|
||||
|
|
||||
63 | async def test_with_default_value(
|
||||
64 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
|
||||
65 | param: str = Query("default", description="Test"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
66 | ) -> str:
|
||||
67 | return param
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
63 | @app.get("/test7")
|
||||
64 | async def test_with_default_value(
|
||||
65 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
|
||||
- param: str = Query("default", description="Test"),
|
||||
66 + param: Annotated[str, Query(description="Test")] = "default",
|
||||
67 | ) -> str:
|
||||
68 | return param
|
||||
69 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:73:5
|
||||
|
|
||||
71 | async def test_with_default_none(
|
||||
72 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
|
||||
73 | param: str | None = Query(None, description="Test"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
74 | ) -> str:
|
||||
75 | return param or "empty"
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
71 | @app.get("/test8")
|
||||
72 | async def test_with_default_none(
|
||||
73 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
|
||||
- param: str | None = Query(None, description="Test"),
|
||||
74 + param: Annotated[str | None, Query(description="Test")] = None,
|
||||
75 | ) -> str:
|
||||
76 | return param or "empty"
|
||||
77 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:81:5
|
||||
|
|
||||
79 | async def test_mixed_parameters(
|
||||
80 | # First param should be fixed with default preserved
|
||||
81 | optional_param: str = Query("default", description="Optional"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 | # Second param should not be fixed because of the preceding default
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
79 | @app.get("/test9")
|
||||
80 | async def test_mixed_parameters(
|
||||
81 | # First param should be fixed with default preserved
|
||||
- optional_param: str = Query("default", description="Optional"),
|
||||
82 + optional_param: Annotated[str, Query(description="Optional")] = "default",
|
||||
83 | # Second param should not be fixed because of the preceding default
|
||||
84 | required_param: str = Query(..., description="Required"),
|
||||
85 | # Third param should be fixed with default preserved
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:83:5
|
||||
|
|
||||
81 | optional_param: str = Query("default", description="Optional"),
|
||||
82 | # Second param should not be fixed because of the preceding default
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
84 | # Third param should be fixed with default preserved
|
||||
85 | another_optional_param: int = Query(42, description="Another optional"),
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:85:5
|
||||
|
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
84 | # Third param should be fixed with default preserved
|
||||
85 | another_optional_param: int = Query(42, description="Another optional"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
86 | ) -> str:
|
||||
87 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
||||
|
|
||||
help: Replace with `typing.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
83 | # Second param should not be fixed because of the preceding default
|
||||
84 | required_param: str = Query(..., description="Required"),
|
||||
85 | # Third param should be fixed with default preserved
|
||||
- another_optional_param: int = Query(42, description="Another optional"),
|
||||
86 + another_optional_param: Annotated[int, Query(description="Another optional")] = 42,
|
||||
87 | ) -> str:
|
||||
88 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/fastapi/mod.rs
|
||||
---
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:14:5
|
||||
|
|
||||
12 | async def test_ellipsis_query(
|
||||
13 | # This should become: param: Annotated[str, Query(description="Test param")]
|
||||
14 | param: str = Query(..., description="Test param"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
15 | ) -> str:
|
||||
16 | return param
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
12 | @app.get("/test1")
|
||||
13 | async def test_ellipsis_query(
|
||||
14 | # This should become: param: Annotated[str, Query(description="Test param")]
|
||||
- param: str = Query(..., description="Test param"),
|
||||
15 + param: Annotated[str, Query(description="Test param")],
|
||||
16 | ) -> str:
|
||||
17 | return param
|
||||
18 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:22:5
|
||||
|
|
||||
20 | async def test_ellipsis_header(
|
||||
21 | # This should become: auth: Annotated[str, Header(description="Auth header")]
|
||||
22 | auth: str = Header(..., description="Auth header"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
23 | ) -> str:
|
||||
24 | return auth
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
20 | @app.get("/test2")
|
||||
21 | async def test_ellipsis_header(
|
||||
22 | # This should become: auth: Annotated[str, Header(description="Auth header")]
|
||||
- auth: str = Header(..., description="Auth header"),
|
||||
23 + auth: Annotated[str, Header(description="Auth header")],
|
||||
24 | ) -> str:
|
||||
25 | return auth
|
||||
26 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:30:5
|
||||
|
|
||||
28 | async def test_ellipsis_body(
|
||||
29 | # This should become: data: Annotated[dict, Body(description="Request body")]
|
||||
30 | data: dict = Body(..., description="Request body"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 | ) -> dict:
|
||||
32 | return data
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
28 | @app.post("/test3")
|
||||
29 | async def test_ellipsis_body(
|
||||
30 | # This should become: data: Annotated[dict, Body(description="Request body")]
|
||||
- data: dict = Body(..., description="Request body"),
|
||||
31 + data: Annotated[dict, Body(description="Request body")],
|
||||
32 | ) -> dict:
|
||||
33 | return data
|
||||
34 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:38:5
|
||||
|
|
||||
36 | async def test_ellipsis_cookie(
|
||||
37 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
|
||||
38 | session: str = Cookie(..., description="Session ID"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
39 | ) -> str:
|
||||
40 | return session
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
36 | @app.get("/test4")
|
||||
37 | async def test_ellipsis_cookie(
|
||||
38 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
|
||||
- session: str = Cookie(..., description="Session ID"),
|
||||
39 + session: Annotated[str, Cookie(description="Session ID")],
|
||||
40 | ) -> str:
|
||||
41 | return session
|
||||
42 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:46:5
|
||||
|
|
||||
44 | async def test_simple_ellipsis(
|
||||
45 | # This should become: id: Annotated[str, Query()]
|
||||
46 | id: str = Query(...),
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
47 | ) -> str:
|
||||
48 | return id
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
44 | @app.get("/test5")
|
||||
45 | async def test_simple_ellipsis(
|
||||
46 | # This should become: id: Annotated[str, Query()]
|
||||
- id: str = Query(...),
|
||||
47 + id: Annotated[str, Query()],
|
||||
48 | ) -> str:
|
||||
49 | return id
|
||||
50 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:54:5
|
||||
|
|
||||
52 | async def test_multiple_kwargs_with_ellipsis(
|
||||
53 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
|
||||
54 | param: str = Query(..., description="Test", min_length=1, max_length=10),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
55 | ) -> str:
|
||||
56 | return param
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
52 | @app.get("/test6")
|
||||
53 | async def test_multiple_kwargs_with_ellipsis(
|
||||
54 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
|
||||
- param: str = Query(..., description="Test", min_length=1, max_length=10),
|
||||
55 + param: Annotated[str, Query(description="Test", min_length=1, max_length=10)],
|
||||
56 | ) -> str:
|
||||
57 | return param
|
||||
58 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:65:5
|
||||
|
|
||||
63 | async def test_with_default_value(
|
||||
64 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
|
||||
65 | param: str = Query("default", description="Test"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
66 | ) -> str:
|
||||
67 | return param
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
63 | @app.get("/test7")
|
||||
64 | async def test_with_default_value(
|
||||
65 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
|
||||
- param: str = Query("default", description="Test"),
|
||||
66 + param: Annotated[str, Query(description="Test")] = "default",
|
||||
67 | ) -> str:
|
||||
68 | return param
|
||||
69 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:73:5
|
||||
|
|
||||
71 | async def test_with_default_none(
|
||||
72 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
|
||||
73 | param: str | None = Query(None, description="Test"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
74 | ) -> str:
|
||||
75 | return param or "empty"
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
71 | @app.get("/test8")
|
||||
72 | async def test_with_default_none(
|
||||
73 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
|
||||
- param: str | None = Query(None, description="Test"),
|
||||
74 + param: Annotated[str | None, Query(description="Test")] = None,
|
||||
75 | ) -> str:
|
||||
76 | return param or "empty"
|
||||
77 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:81:5
|
||||
|
|
||||
79 | async def test_mixed_parameters(
|
||||
80 | # First param should be fixed with default preserved
|
||||
81 | optional_param: str = Query("default", description="Optional"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 | # Second param should not be fixed because of the preceding default
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
79 | @app.get("/test9")
|
||||
80 | async def test_mixed_parameters(
|
||||
81 | # First param should be fixed with default preserved
|
||||
- optional_param: str = Query("default", description="Optional"),
|
||||
82 + optional_param: Annotated[str, Query(description="Optional")] = "default",
|
||||
83 | # Second param should not be fixed because of the preceding default
|
||||
84 | required_param: str = Query(..., description="Required"),
|
||||
85 | # Third param should be fixed with default preserved
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST002 FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:83:5
|
||||
|
|
||||
81 | optional_param: str = Query("default", description="Optional"),
|
||||
82 | # Second param should not be fixed because of the preceding default
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
84 | # Third param should be fixed with default preserved
|
||||
85 | another_optional_param: int = Query(42, description="Another optional"),
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
|
||||
FAST002 [*] FastAPI dependency without `Annotated`
|
||||
--> FAST002_2.py:85:5
|
||||
|
|
||||
83 | required_param: str = Query(..., description="Required"),
|
||||
84 | # Third param should be fixed with default preserved
|
||||
85 | another_optional_param: int = Query(42, description="Another optional"),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
86 | ) -> str:
|
||||
87 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
||||
|
|
||||
help: Replace with `typing_extensions.Annotated`
|
||||
1 | """Test FAST002 ellipsis handling."""
|
||||
2 |
|
||||
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
|
||||
4 + from typing_extensions import Annotated
|
||||
5 |
|
||||
6 | app = FastAPI()
|
||||
7 |
|
||||
--------------------------------------------------------------------------------
|
||||
83 | # Second param should not be fixed because of the preceding default
|
||||
84 | required_param: str = Query(..., description="Required"),
|
||||
85 | # Third param should be fixed with default preserved
|
||||
- another_optional_param: int = Query(42, description="Another optional"),
|
||||
86 + another_optional_param: Annotated[int, Query(description="Another optional")] = 42,
|
||||
87 | ) -> str:
|
||||
88 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -363,3 +363,26 @@ help: Add `id` to function signature
|
||||
206 |
|
||||
207 | # No errors
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
FAST003 Parameter `import` appears in route path, but not in `get_import` signature
|
||||
--> FAST003.py:261:20
|
||||
|
|
||||
260 | # https://github.com/astral-sh/ruff/issues/20941
|
||||
261 | @app.get("/imports/{import}")
|
||||
| ^^^^^^^^
|
||||
262 | async def get_import():
|
||||
263 | ...
|
||||
|
|
||||
help: Add `import` to function signature
|
||||
|
||||
FAST003 Parameter `__debug__` appears in route path, but not in `get_debug` signature
|
||||
--> FAST003.py:265:18
|
||||
|
|
||||
263 | ...
|
||||
264 |
|
||||
265 | @app.get("/debug/{__debug__}")
|
||||
| ^^^^^^^^^^^
|
||||
266 | async def get_debug():
|
||||
267 | ...
|
||||
|
|
||||
help: Add `__debug__` to function signature
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables.
|
||||
/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables, or any starred argument.
|
||||
///
|
||||
/// This rule applies to Python 3.14 and later, where `map` accepts a `strict` keyword
|
||||
/// argument. For details, see: [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html).
|
||||
@@ -62,7 +62,12 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "map")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& call.arguments.args.len() >= 3 // function + at least 2 iterables
|
||||
&& (
|
||||
// at least 2 iterables (+ 1 function)
|
||||
call.arguments.args.len() >= 3
|
||||
// or a starred argument
|
||||
|| call.arguments.args.iter().any(ast::Expr::is_starred_expr)
|
||||
)
|
||||
&& !any_infinite_iterables(call.arguments.args.iter().skip(1), semantic)
|
||||
{
|
||||
checker
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `zip` calls without an explicit `strict` parameter.
|
||||
/// Checks for `zip` calls without an explicit `strict` parameter when called with two or more iterables, or any starred argument.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By default, if the iterables passed to `zip` are of different lengths, the
|
||||
@@ -58,6 +58,12 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "zip")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& (
|
||||
// at least 2 iterables
|
||||
call.arguments.args.len() >= 2
|
||||
// or a starred argument
|
||||
|| call.arguments.args.iter().any(ast::Expr::is_starred_expr)
|
||||
)
|
||||
&& !any_infinite_iterables(call.arguments.args.iter(), semantic)
|
||||
{
|
||||
checker
|
||||
|
||||
@@ -1,204 +1,140 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
assertion_line: 156
|
||||
---
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:4:1
|
||||
|
|
||||
3 | # Errors
|
||||
4 | zip()
|
||||
| ^^^^^
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
4 | zip("a", "b")
|
||||
| ^^^^^^^^^^^^^
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
1 | from itertools import count, cycle, repeat
|
||||
2 |
|
||||
3 | # Errors
|
||||
- zip()
|
||||
4 + zip(strict=False)
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
- zip("a", "b")
|
||||
4 + zip("a", "b", strict=False)
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:5:1
|
||||
|
|
||||
3 | # Errors
|
||||
4 | zip()
|
||||
5 | zip(range(3))
|
||||
| ^^^^^^^^^^^^^
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
4 | zip("a", "b")
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
2 |
|
||||
3 | # Errors
|
||||
4 | zip()
|
||||
- zip(range(3))
|
||||
5 + zip(range(3), strict=False)
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
4 | zip("a", "b")
|
||||
- zip("a", "b", *zip("c"))
|
||||
5 + zip("a", "b", *zip("c"), strict=False)
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
8 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:6:1
|
||||
--> B905.py:6:5
|
||||
|
|
||||
4 | zip()
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
| ^^^^^^^^^^^^^
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
4 | zip("a", "b")
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
| ^^^^^^^^^^^^^
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
3 | # Errors
|
||||
4 | zip()
|
||||
5 | zip(range(3))
|
||||
- zip("a", "b")
|
||||
6 + zip("a", "b", strict=False)
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
4 | zip("a", "b")
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
- zip(zip("a", "b"), strict=False)
|
||||
6 + zip(zip("a", "b", strict=False), strict=False)
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
8 |
|
||||
9 | # OK
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:7:1
|
||||
|
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
7 | zip(zip("a", strict=True),"b")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 |
|
||||
9 | # OK
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
4 | zip()
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
- zip("a", "b", *zip("c"))
|
||||
7 + zip("a", "b", *zip("c"), strict=False)
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
4 | zip("a", "b")
|
||||
5 | zip("a", "b", *zip("c"))
|
||||
6 | zip(zip("a", "b"), strict=False)
|
||||
- zip(zip("a", strict=True),"b")
|
||||
7 + zip(zip("a", strict=True),"b", strict=False)
|
||||
8 |
|
||||
9 | # OK
|
||||
10 | zip(range(3), strict=True)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:7:16
|
||||
|
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
| ^^^^^^^^
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
4 | zip()
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
- zip("a", "b", *zip("c"))
|
||||
7 + zip("a", "b", *zip("c", strict=False))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:8:5
|
||||
|
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
| ^^^^^^^^
|
||||
9 | zip(zip("a", strict=True))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
- zip(zip("a"), strict=False)
|
||||
8 + zip(zip("a", strict=False), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
11 | # OK
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:9:1
|
||||
--> B905.py:22:1
|
||||
|
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10 |
|
||||
11 | # OK
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
- zip(zip("a", strict=True))
|
||||
9 + zip(zip("a", strict=True), strict=False)
|
||||
10 |
|
||||
11 | # OK
|
||||
12 | zip(range(3), strict=True)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:24:1
|
||||
|
|
||||
23 | # Errors (limited iterators).
|
||||
24 | zip([1, 2, 3], repeat(1, 1))
|
||||
21 | # Errors (limited iterators).
|
||||
22 | zip([1, 2, 3], repeat(1, 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
25 | zip([1, 2, 3], repeat(1, times=4))
|
||||
23 | zip([1, 2, 3], repeat(1, times=4))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
21 | zip([1, 2, 3], repeat(1, times=None))
|
||||
22 |
|
||||
23 | # Errors (limited iterators).
|
||||
19 | zip([1, 2, 3], repeat(1, times=None))
|
||||
20 |
|
||||
21 | # Errors (limited iterators).
|
||||
- zip([1, 2, 3], repeat(1, 1))
|
||||
24 + zip([1, 2, 3], repeat(1, 1), strict=False)
|
||||
25 | zip([1, 2, 3], repeat(1, times=4))
|
||||
26 |
|
||||
27 | import builtins
|
||||
22 + zip([1, 2, 3], repeat(1, 1), strict=False)
|
||||
23 | zip([1, 2, 3], repeat(1, times=4))
|
||||
24 |
|
||||
25 | import builtins
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:25:1
|
||||
--> B905.py:23:1
|
||||
|
|
||||
23 | # Errors (limited iterators).
|
||||
24 | zip([1, 2, 3], repeat(1, 1))
|
||||
25 | zip([1, 2, 3], repeat(1, times=4))
|
||||
21 | # Errors (limited iterators).
|
||||
22 | zip([1, 2, 3], repeat(1, 1))
|
||||
23 | zip([1, 2, 3], repeat(1, times=4))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
26 |
|
||||
27 | import builtins
|
||||
24 |
|
||||
25 | import builtins
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
22 |
|
||||
23 | # Errors (limited iterators).
|
||||
24 | zip([1, 2, 3], repeat(1, 1))
|
||||
20 |
|
||||
21 | # Errors (limited iterators).
|
||||
22 | zip([1, 2, 3], repeat(1, 1))
|
||||
- zip([1, 2, 3], repeat(1, times=4))
|
||||
25 + zip([1, 2, 3], repeat(1, times=4), strict=False)
|
||||
26 |
|
||||
27 | import builtins
|
||||
28 | # Still an error even though it uses the qualified name
|
||||
23 + zip([1, 2, 3], repeat(1, times=4), strict=False)
|
||||
24 |
|
||||
25 | import builtins
|
||||
26 | # Still an error even though it uses the qualified name
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:29:1
|
||||
--> B905.py:34:1
|
||||
|
|
||||
27 | import builtins
|
||||
28 | # Still an error even though it uses the qualified name
|
||||
29 | builtins.zip([1, 2, 3])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
32 | zip(range(3))
|
||||
33 | # Error
|
||||
34 | zip(*lot_of_iterators)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
26 |
|
||||
27 | import builtins
|
||||
28 | # Still an error even though it uses the qualified name
|
||||
- builtins.zip([1, 2, 3])
|
||||
29 + builtins.zip([1, 2, 3], strict=False)
|
||||
31 | zip()
|
||||
32 | zip(range(3))
|
||||
33 | # Error
|
||||
- zip(*lot_of_iterators)
|
||||
34 + zip(*lot_of_iterators, strict=False)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
assertion_line: 112
|
||||
---
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:5:1
|
||||
@@ -147,3 +146,18 @@ help: Add explicit value for parameter `strict=`
|
||||
19 | # OK
|
||||
20 | map(lambda x: x, [1, 2, 3], strict=True)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:36:1
|
||||
|
|
||||
35 | # Regression https://github.com/astral-sh/ruff/issues/20997
|
||||
36 | map(f, *lots_of_iterators)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
33 | map(lambda x, y: x + y, [1, 2, 3], count())
|
||||
34 |
|
||||
35 | # Regression https://github.com/astral-sh/ruff/issues/20997
|
||||
- map(f, *lots_of_iterators)
|
||||
36 + map(f, *lots_of_iterators, strict=False)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,17 +1,50 @@
|
||||
//! Rules from [flake8-gettext](https://pypi.org/project/flake8-gettext/).
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_extended_i18n_function_matching_enabled;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::Modules;
|
||||
|
||||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[Name]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
pub(crate) fn is_gettext_func_call(
|
||||
checker: &Checker,
|
||||
func: &Expr,
|
||||
functions_names: &[Name],
|
||||
) -> bool {
|
||||
if func
|
||||
.as_name_expr()
|
||||
.map(ast::ExprName::id)
|
||||
.is_some_and(|id| functions_names.contains(id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if !is_extended_i18n_function_matching_enabled(checker.settings()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let Some(qualified_name) = semantic.resolve_qualified_name(func) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if semantic.seen_module(Modules::BUILTINS)
|
||||
&& matches!(
|
||||
qualified_name.segments(),
|
||||
["builtins", id] if functions_names.contains(&Name::new(id)),
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["gettext", "gettext" | "ngettext"]
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -22,6 +55,7 @@ mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -37,4 +71,20 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::FStringInGetTextFuncCall, Path::new("INT001.py"))]
|
||||
#[test_case(Rule::FormatInGetTextFuncCall, Path::new("INT002.py"))]
|
||||
#[test_case(Rule::PrintfInGetTextFuncCall, Path::new("INT003.py"))]
|
||||
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview__{}_{}", rule_code.name(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_gettext").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,85 @@ INT001 f-string is resolved before function call; consider `_("string %s") % arg
|
||||
2 |
|
||||
3 | # Don't trigger for t-strings
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:6:9
|
||||
|
|
||||
4 | _(t"{'value'}")
|
||||
5 |
|
||||
6 | gettext(f"{'value'}")
|
||||
| ^^^^^^^^^^^^
|
||||
7 | ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:7:10
|
||||
|
|
||||
6 | gettext(f"{'value'}")
|
||||
7 | ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
| ^^^^^^^^^^^^
|
||||
8 |
|
||||
9 | _gettext(f"{'value'}") # no lint
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:31:14
|
||||
|
|
||||
29 | def _(message): return message
|
||||
30 |
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:32:14
|
||||
|
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
32 | _(f"{'albatross'}"),
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:33:14
|
||||
|
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
| ^^^^^^^^^^
|
||||
34 | _(f"{'penguin'}"),
|
||||
35 | _(f"{'python'}"),]
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:34:14
|
||||
|
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
| ^^^^^^^^^^^^^^
|
||||
35 | _(f"{'python'}"),]
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:35:14
|
||||
|
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
35 | _(f"{'python'}"),]
|
||||
| ^^^^^^^^^^^^^
|
||||
36 |
|
||||
37 | del _
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:41:13
|
||||
|
|
||||
39 | for a in animals:
|
||||
40 | print(_(a))
|
||||
41 | print(_(f"{a}"))
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
@@ -6,4 +6,88 @@ INT002 `format` method argument is resolved before function call; consider `_("s
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
2 | gettext("{}".format("line"))
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:2:9
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
2 | gettext("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:3:10
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
2 | gettext("{}".format("line"))
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | _gettext("{}".format("line")) # no lint
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:27:14
|
||||
|
|
||||
25 | def _(message): return message
|
||||
26 |
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:28:14
|
||||
|
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
28 | _("{}".format("albatross")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:29:14
|
||||
|
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
30 | _("{}".format("penguin")),
|
||||
31 | _("{}".format("python")),]
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:30:14
|
||||
|
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 | _("{}".format("python")),]
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:31:14
|
||||
|
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
31 | _("{}".format("python")),]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | del _
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:37:13
|
||||
|
|
||||
35 | for a in animals:
|
||||
36 | print(_(a))
|
||||
37 | print(_("{}".format(a)))
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_gettext/mod.rs
|
||||
---
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:1:3
|
||||
|
|
||||
1 | _(f"{'value'}")
|
||||
| ^^^^^^^^^^^^
|
||||
2 |
|
||||
3 | # Don't trigger for t-strings
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:6:9
|
||||
|
|
||||
4 | _(t"{'value'}")
|
||||
5 |
|
||||
6 | gettext(f"{'value'}")
|
||||
| ^^^^^^^^^^^^
|
||||
7 | ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:7:10
|
||||
|
|
||||
6 | gettext(f"{'value'}")
|
||||
7 | ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
| ^^^^^^^^^^^^
|
||||
8 |
|
||||
9 | _gettext(f"{'value'}") # no lint
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:22:21
|
||||
|
|
||||
20 | name = "Guido"
|
||||
21 |
|
||||
22 | gettext_mod.gettext(f"Hello, {name}!")
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
23 | gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
24 | gettext_fn(f"Hello, {name}!")
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:23:22
|
||||
|
|
||||
22 | gettext_mod.gettext(f"Hello, {name}!")
|
||||
23 | gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
24 | gettext_fn(f"Hello, {name}!")
|
||||
25 | ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:24:12
|
||||
|
|
||||
22 | gettext_mod.gettext(f"Hello, {name}!")
|
||||
23 | gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
24 | gettext_fn(f"Hello, {name}!")
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
25 | ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:25:13
|
||||
|
|
||||
23 | gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
24 | gettext_fn(f"Hello, {name}!")
|
||||
25 | ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:31:14
|
||||
|
|
||||
29 | def _(message): return message
|
||||
30 |
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:32:14
|
||||
|
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
32 | _(f"{'albatross'}"),
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:33:14
|
||||
|
|
||||
31 | animals = [_(f"{'mollusk'}"),
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
| ^^^^^^^^^^
|
||||
34 | _(f"{'penguin'}"),
|
||||
35 | _(f"{'python'}"),]
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:34:14
|
||||
|
|
||||
32 | _(f"{'albatross'}"),
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
| ^^^^^^^^^^^^^^
|
||||
35 | _(f"{'python'}"),]
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:35:14
|
||||
|
|
||||
33 | _(f"{'rat'}"),
|
||||
34 | _(f"{'penguin'}"),
|
||||
35 | _(f"{'python'}"),]
|
||||
| ^^^^^^^^^^^^^
|
||||
36 |
|
||||
37 | del _
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:41:13
|
||||
|
|
||||
39 | for a in animals:
|
||||
40 | print(_(a))
|
||||
41 | print(_(f"{a}"))
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:51:12
|
||||
|
|
||||
49 | builtins.__dict__["gettext"] = gettext_fn
|
||||
50 |
|
||||
51 | builtins._(f"{'value'}")
|
||||
| ^^^^^^^^^^^^
|
||||
52 | builtins.gettext(f"{'value'}")
|
||||
53 | builtins.ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:52:18
|
||||
|
|
||||
51 | builtins._(f"{'value'}")
|
||||
52 | builtins.gettext(f"{'value'}")
|
||||
| ^^^^^^^^^^^^
|
||||
53 | builtins.ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
|
|
||||
|
||||
INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT001.py:53:19
|
||||
|
|
||||
51 | builtins._(f"{'value'}")
|
||||
52 | builtins.gettext(f"{'value'}")
|
||||
53 | builtins.ngettext(f"{'value'}", f"{'values'}", 2)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_gettext/mod.rs
|
||||
---
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:1:3
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
2 | gettext("{}".format("line"))
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:2:9
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
2 | gettext("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:3:10
|
||||
|
|
||||
1 | _("{}".format("line"))
|
||||
2 | gettext("{}".format("line"))
|
||||
3 | ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | _gettext("{}".format("line")) # no lint
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:18:21
|
||||
|
|
||||
16 | name = "Guido"
|
||||
17 |
|
||||
18 | gettext_mod.gettext("Hello, {}!".format(name))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
19 | gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
20 | gettext_fn("Hello, {}!".format(name))
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:19:22
|
||||
|
|
||||
18 | gettext_mod.gettext("Hello, {}!".format(name))
|
||||
19 | gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
20 | gettext_fn("Hello, {}!".format(name))
|
||||
21 | ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:20:12
|
||||
|
|
||||
18 | gettext_mod.gettext("Hello, {}!".format(name))
|
||||
19 | gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
20 | gettext_fn("Hello, {}!".format(name))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
21 | ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:21:13
|
||||
|
|
||||
19 | gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
20 | gettext_fn("Hello, {}!".format(name))
|
||||
21 | ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:27:14
|
||||
|
|
||||
25 | def _(message): return message
|
||||
26 |
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:28:14
|
||||
|
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
28 | _("{}".format("albatross")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:29:14
|
||||
|
|
||||
27 | animals = [_("{}".format("mollusk")),
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
30 | _("{}".format("penguin")),
|
||||
31 | _("{}".format("python")),]
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:30:14
|
||||
|
|
||||
28 | _("{}".format("albatross")),
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 | _("{}".format("python")),]
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:31:14
|
||||
|
|
||||
29 | _("{}".format("rat")),
|
||||
30 | _("{}".format("penguin")),
|
||||
31 | _("{}".format("python")),]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | del _
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:37:13
|
||||
|
|
||||
35 | for a in animals:
|
||||
36 | print(_(a))
|
||||
37 | print(_("{}".format(a)))
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:47:12
|
||||
|
|
||||
45 | builtins.__dict__["gettext"] = gettext_fn
|
||||
46 |
|
||||
47 | builtins._("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
48 | builtins.gettext("{}".format("line"))
|
||||
49 | builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:48:18
|
||||
|
|
||||
47 | builtins._("{}".format("line"))
|
||||
48 | builtins.gettext("{}".format("line"))
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
49 | builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
|
|
||||
|
||||
INT002 `format` method argument is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT002.py:49:19
|
||||
|
|
||||
47 | builtins._("{}".format("line"))
|
||||
48 | builtins.gettext("{}".format("line"))
|
||||
49 | builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_gettext/mod.rs
|
||||
---
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:1:3
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
2 | gettext("%s" % "line")
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:2:9
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
2 | gettext("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:3:10
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
2 | gettext("%s" % "line")
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
| ^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | _gettext("%s" % "line") # no lint
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:18:21
|
||||
|
|
||||
16 | name = "Guido"
|
||||
17 |
|
||||
18 | gettext_mod.gettext("Hello, %s!" % name)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
19 | gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
20 | gettext_fn("Hello, %s!" % name)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:19:22
|
||||
|
|
||||
18 | gettext_mod.gettext("Hello, %s!" % name)
|
||||
19 | gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
20 | gettext_fn("Hello, %s!" % name)
|
||||
21 | ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:20:12
|
||||
|
|
||||
18 | gettext_mod.gettext("Hello, %s!" % name)
|
||||
19 | gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
20 | gettext_fn("Hello, %s!" % name)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
21 | ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:21:13
|
||||
|
|
||||
19 | gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
20 | gettext_fn("Hello, %s!" % name)
|
||||
21 | ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:27:14
|
||||
|
|
||||
25 | def _(message): return message
|
||||
26 |
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
| ^^^^^^^^^^^^^^^
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:28:14
|
||||
|
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
28 | _("%s" %"albatross"),
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:29:14
|
||||
|
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
| ^^^^^^^^^^^
|
||||
30 | _("%s" %"penguin"),
|
||||
31 | _("%s" %"python"),]
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:30:14
|
||||
|
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
| ^^^^^^^^^^^^^^^
|
||||
31 | _("%s" %"python"),]
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:31:14
|
||||
|
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
31 | _("%s" %"python"),]
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | del _
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:37:13
|
||||
|
|
||||
35 | for a in animals:
|
||||
36 | print(_(a))
|
||||
37 | print(_("%s" % a))
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:47:12
|
||||
|
|
||||
45 | builtins.__dict__["gettext"] = gettext_fn
|
||||
46 |
|
||||
47 | builtins._("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
48 | builtins.gettext("%s" % "line")
|
||||
49 | builtins.ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:48:18
|
||||
|
|
||||
47 | builtins._("%s" % "line")
|
||||
48 | builtins.gettext("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
49 | builtins.ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:49:19
|
||||
|
|
||||
47 | builtins._("%s" % "line")
|
||||
48 | builtins.gettext("%s" % "line")
|
||||
49 | builtins.ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
@@ -6,4 +6,88 @@ INT003 printf-style format is resolved before function call; consider `_("string
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
2 | gettext("%s" % "line")
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:2:9
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
2 | gettext("%s" % "line")
|
||||
| ^^^^^^^^^^^^^
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:3:10
|
||||
|
|
||||
1 | _("%s" % "line")
|
||||
2 | gettext("%s" % "line")
|
||||
3 | ngettext("%s" % "line", "%s" % "lines", 2)
|
||||
| ^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | _gettext("%s" % "line") # no lint
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:27:14
|
||||
|
|
||||
25 | def _(message): return message
|
||||
26 |
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
| ^^^^^^^^^^^^^^^
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:28:14
|
||||
|
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
28 | _("%s" %"albatross"),
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:29:14
|
||||
|
|
||||
27 | animals = [_("%s" %"mollusk"),
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
| ^^^^^^^^^^^
|
||||
30 | _("%s" %"penguin"),
|
||||
31 | _("%s" %"python"),]
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:30:14
|
||||
|
|
||||
28 | _("%s" %"albatross"),
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
| ^^^^^^^^^^^^^^^
|
||||
31 | _("%s" %"python"),]
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:31:14
|
||||
|
|
||||
29 | _("%s" %"rat"),
|
||||
30 | _("%s" %"penguin"),
|
||||
31 | _("%s" %"python"),]
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | del _
|
||||
|
|
||||
|
||||
INT003 printf-style format is resolved before function call; consider `_("string %s") % arg`
|
||||
--> INT003.py:37:13
|
||||
|
|
||||
35 | for a in animals:
|
||||
36 | print(_(a))
|
||||
37 | print(_("%s" % a))
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -165,16 +165,17 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, expr: &Expr) {
|
||||
match attr.as_str() {
|
||||
// PD007
|
||||
"ix" if checker.is_rule_enabled(Rule::PandasUseOfDotIx) => {
|
||||
checker.report_diagnostic(PandasUseOfDotIx, range)
|
||||
let mut diagnostic = checker.report_diagnostic(PandasUseOfDotIx, range);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
}
|
||||
// PD008
|
||||
"at" if checker.is_rule_enabled(Rule::PandasUseOfDotAt) => {
|
||||
checker.report_diagnostic(PandasUseOfDotAt, range)
|
||||
checker.report_diagnostic(PandasUseOfDotAt, range);
|
||||
}
|
||||
// PD009
|
||||
"iat" if checker.is_rule_enabled(Rule::PandasUseOfDotIat) => {
|
||||
checker.report_diagnostic(PandasUseOfDotIat, range)
|
||||
checker.report_diagnostic(PandasUseOfDotIat, range);
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ pub(crate) fn redefined_while_unused(checker: &Checker, scope_id: ScopeId, scope
|
||||
},
|
||||
binding.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
|
||||
diagnostic.secondary_annotation(
|
||||
format_args!("previous definition of `{name}` here"),
|
||||
|
||||
@@ -145,7 +145,6 @@ fn parenthesize(expr: &Expr, text: &str, context: FormatContext) -> bool {
|
||||
Expr::BinOp(_)
|
||||
| Expr::UnaryOp(_)
|
||||
| Expr::BoolOp(_)
|
||||
| Expr::Named(_)
|
||||
| Expr::Compare(_)
|
||||
| Expr::If(_)
|
||||
| Expr::Lambda(_)
|
||||
@@ -167,6 +166,7 @@ fn parenthesize(expr: &Expr, text: &str, context: FormatContext) -> bool {
|
||||
_,
|
||||
Expr::Generator(_)
|
||||
| Expr::Dict(_)
|
||||
| Expr::Named(_)
|
||||
| Expr::Set(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_),
|
||||
|
||||
@@ -1320,6 +1320,8 @@ UP032 [*] Use f-string instead of `format` call
|
||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
271 | x: "'{} + {}'.format(x, y)"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
272 |
|
||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
|
|
||||
help: Convert to f-string
|
||||
268 |
|
||||
@@ -1327,3 +1329,23 @@ help: Convert to f-string
|
||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
- x: "'{} + {}'.format(x, y)"
|
||||
271 + x: "f'{x} + {y}'"
|
||||
272 |
|
||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
274 | # Fix should parenthesize walrus
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:277:14
|
||||
|
|
||||
275 | if __name__ == "__main__":
|
||||
276 | number = 0
|
||||
277 | string = "{}".format(number := number + 1)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
278 | print(string)
|
||||
|
|
||||
help: Convert to f-string
|
||||
274 | # Fix should parenthesize walrus
|
||||
275 | if __name__ == "__main__":
|
||||
276 | number = 0
|
||||
- string = "{}".format(number := number + 1)
|
||||
277 + string = f"{(number := number + 1)}"
|
||||
278 | print(string)
|
||||
|
||||
@@ -617,12 +617,12 @@ impl TryFrom<String> for RequiredVersion {
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for RequiredVersion {
|
||||
fn schema_name() -> String {
|
||||
"RequiredVersion".to_string()
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("RequiredVersion")
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
generator.subschema_for::<String>()
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
<String as schemars::JsonSchema>::json_schema(generator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::SourceFile;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::{codes::Rule, message::create_lint_diagnostic};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
pub enum FixAvailability {
|
||||
Sometimes,
|
||||
Always,
|
||||
|
||||
@@ -30,10 +30,11 @@ rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars"]
|
||||
schemars = ["dep:schemars", "dep:serde_json"]
|
||||
cache = ["dep:ruff_cache", "dep:ruff_macros"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
|
||||
@@ -11,6 +11,11 @@ use crate::generated::ExprName;
|
||||
#[cfg_attr(feature = "cache", derive(ruff_macros::CacheKey))]
|
||||
#[cfg_attr(feature = "salsa", derive(salsa::Update))]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
#[cfg_attr(
|
||||
feature = "schemars",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(with = "String")
|
||||
)]
|
||||
pub struct Name(compact_str::CompactString);
|
||||
|
||||
impl Name {
|
||||
@@ -201,35 +206,6 @@ impl PartialEq<Name> for &String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for Name {
|
||||
fn is_referenceable() -> bool {
|
||||
String::is_referenceable()
|
||||
}
|
||||
|
||||
fn schema_name() -> String {
|
||||
String::schema_name()
|
||||
}
|
||||
|
||||
fn schema_id() -> std::borrow::Cow<'static, str> {
|
||||
String::schema_id()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
String::json_schema(generator)
|
||||
}
|
||||
|
||||
fn _schemars_private_non_optional_json_schema(
|
||||
generator: &mut schemars::r#gen::SchemaGenerator,
|
||||
) -> schemars::schema::Schema {
|
||||
String::_schemars_private_non_optional_json_schema(generator)
|
||||
}
|
||||
|
||||
fn _schemars_private_is_option() -> bool {
|
||||
String::_schemars_private_is_option()
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of a qualified name, like `typing.List`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct QualifiedName<'a>(SegmentsVec<'a>);
|
||||
|
||||
@@ -188,42 +188,39 @@ mod serde {
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schemars {
|
||||
use super::PythonVersion;
|
||||
use schemars::_serde_json::Value;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::schema::{Metadata, Schema, SchemaObject, SubschemaValidation};
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator};
|
||||
use serde_json::Value;
|
||||
|
||||
impl JsonSchema for PythonVersion {
|
||||
fn schema_name() -> String {
|
||||
"PythonVersion".to_string()
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("PythonVersion")
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
let sub_schemas = std::iter::once(Schema::Object(SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
string: Some(Box::new(schemars::schema::StringValidation {
|
||||
pattern: Some(r"^\d+\.\d+$".to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.chain(Self::iter().map(|v| {
|
||||
Schema::Object(SchemaObject {
|
||||
const_value: Some(Value::String(v.to_string())),
|
||||
metadata: Some(Box::new(Metadata {
|
||||
description: Some(format!("Python {v}")),
|
||||
..Metadata::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut any_of: Vec<Value> = vec![
|
||||
schemars::json_schema!({
|
||||
"type": "string",
|
||||
"pattern": r"^\d+\.\d+$",
|
||||
})
|
||||
}));
|
||||
.into(),
|
||||
];
|
||||
|
||||
Schema::Object(SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
any_of: Some(sub_schemas.collect()),
|
||||
..Default::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
for version in Self::iter() {
|
||||
let mut schema = schemars::json_schema!({
|
||||
"const": version.to_string(),
|
||||
});
|
||||
schema.ensure_object().insert(
|
||||
"description".to_string(),
|
||||
Value::String(format!("Python {version}")),
|
||||
);
|
||||
any_of.push(schema.into());
|
||||
}
|
||||
|
||||
let mut schema = Schema::default();
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("anyOf".to_string(), Value::Array(any_of));
|
||||
schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -66,7 +67,7 @@ serde = [
|
||||
"ruff_source_file/serde",
|
||||
"ruff_python_ast/serde",
|
||||
]
|
||||
schemars = ["dep:schemars", "ruff_formatter/schemars"]
|
||||
schemars = ["dep:schemars", "dep:serde_json", "ruff_formatter/schemars"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -403,15 +403,12 @@ pub enum DocstringCodeLineWidth {
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use ruff_formatter::LineWidth;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::{Metadata, Schema, SubschemaValidation};
|
||||
use schemars::{Schema, SchemaGenerator};
|
||||
use serde_json::Value;
|
||||
|
||||
/// A dummy type that is used to generate a schema for `DocstringCodeLineWidth::Dynamic`.
|
||||
pub(super) fn dynamic(_: &mut SchemaGenerator) -> Schema {
|
||||
Schema::Object(schemars::schema::SchemaObject {
|
||||
const_value: Some("dynamic".to_string().into()),
|
||||
..Default::default()
|
||||
})
|
||||
schemars::json_schema!({ "const": "dynamic" })
|
||||
}
|
||||
|
||||
// We use a manual schema for `fixed` even thought it isn't strictly necessary according to the
|
||||
@@ -422,19 +419,14 @@ mod schema {
|
||||
// `allOf`. There's no semantic difference between `allOf` and `oneOf` for single element lists.
|
||||
pub(super) fn fixed(generator: &mut SchemaGenerator) -> Schema {
|
||||
let schema = generator.subschema_for::<LineWidth>();
|
||||
Schema::Object(schemars::schema::SchemaObject {
|
||||
metadata: Some(Box::new(Metadata {
|
||||
description: Some(
|
||||
"Wrap docstring code examples at a fixed line width.".to_string(),
|
||||
),
|
||||
..Metadata::default()
|
||||
})),
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
one_of: Some(vec![schema]),
|
||||
..SubschemaValidation::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
let mut schema_object = Schema::default();
|
||||
let map = schema_object.ensure_object();
|
||||
map.insert(
|
||||
"description".to_string(),
|
||||
Value::String("Wrap docstring code examples at a fixed line width.".to_string()),
|
||||
);
|
||||
map.insert("oneOf".to_string(), Value::Array(vec![schema.into()]));
|
||||
schema_object
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ To run the fuzzer, execute the following command
|
||||
(requires [`uv`](https://github.com/astral-sh/uv) to be installed):
|
||||
|
||||
```sh
|
||||
uvx --from ./python/py-fuzzer fuzz
|
||||
uv run --project=./python/py-fuzzer fuzz
|
||||
```
|
||||
|
||||
Refer to the [py-fuzzer](https://github.com/astral-sh/ruff/blob/main/python/py-fuzzer/fuzz.py)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
match x:
|
||||
case [a] | [b]: ...
|
||||
case [a] | []: ...
|
||||
case (x, y) | (x,): ...
|
||||
case [a, _] | [a, b]: ...
|
||||
case (x, (y | z)): ...
|
||||
case [a] | [b] | [c]: ...
|
||||
case [] | [a]: ...
|
||||
case [a] | [C(x)]: ...
|
||||
case [[a] | [b]]: ...
|
||||
case [C(a)] | [C(b)]: ...
|
||||
case [C(D(a))] | [C(D(b))]: ...
|
||||
case [(a, b)] | [(c, d)]: ...
|
||||
@@ -0,0 +1,4 @@
|
||||
# parse_options: {"target-version": "3.11"}
|
||||
# nested interpolations also need to be checked
|
||||
f'{1: abcd "{'aa'}" }'
|
||||
f'{1: abcd "{"\n"}" }'
|
||||
@@ -0,0 +1,6 @@
|
||||
match x:
|
||||
case [a] | [a]: ...
|
||||
case (x, y) | (x, y): ...
|
||||
case (x, (y | y)): ...
|
||||
case [a, _] | [a, _]: ...
|
||||
case [a] | [C(a)]: ...
|
||||
@@ -489,6 +489,23 @@ impl Tokens {
|
||||
TokenIterWithContext::new(&self.raw)
|
||||
}
|
||||
|
||||
/// Performs a binary search to find the index of the **first** token that starts at the given `offset`.
|
||||
///
|
||||
/// Unlike `binary_search_by_key`, this method ensures that if multiple tokens start at the same offset,
|
||||
/// it returns the index of the first one. Multiple tokens can start at the same offset in cases where
|
||||
/// zero-length tokens are involved (like `Dedent` or `Newline` at the end of the file).
|
||||
pub fn binary_search_by_start(&self, offset: TextSize) -> Result<usize, usize> {
|
||||
let partition_point = self.partition_point(|token| token.start() < offset);
|
||||
|
||||
let after = &self[partition_point..];
|
||||
|
||||
if after.first().is_some_and(|first| first.start() == offset) {
|
||||
Ok(partition_point)
|
||||
} else {
|
||||
Err(partition_point)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of [`Token`] that are within the given `range`.
|
||||
///
|
||||
/// The start and end offset of the given range should be either:
|
||||
@@ -532,30 +549,7 @@ impl Tokens {
|
||||
pub fn in_range(&self, range: TextRange) -> &[Token] {
|
||||
let tokens_after_start = self.after(range.start());
|
||||
|
||||
match tokens_after_start.binary_search_by_key(&range.end(), Ranged::end) {
|
||||
Ok(idx) => {
|
||||
// If we found the token with the end offset, that token should be included in the
|
||||
// return slice.
|
||||
&tokens_after_start[..=idx]
|
||||
}
|
||||
Err(idx) => {
|
||||
if let Some(token) = tokens_after_start.get(idx) {
|
||||
// If it's equal to the start offset, then it's at a token boundary which is
|
||||
// valid. If it's less than the start offset, then it's in the gap between the
|
||||
// tokens which is valid as well.
|
||||
assert!(
|
||||
range.end() <= token.start(),
|
||||
"End offset {:?} is inside a token range {:?}",
|
||||
range.end(),
|
||||
token.range()
|
||||
);
|
||||
}
|
||||
|
||||
// This index is where the token with the offset _could_ be, so that token should
|
||||
// be excluded from the return slice.
|
||||
&tokens_after_start[..idx]
|
||||
}
|
||||
}
|
||||
Self::before_impl(tokens_after_start, range.end())
|
||||
}
|
||||
|
||||
/// Searches the token(s) at `offset`.
|
||||
@@ -597,7 +591,7 @@ impl Tokens {
|
||||
/// assert_eq!(collect_tokens(TextSize::new(57)), vec! []);
|
||||
/// ```
|
||||
pub fn at_offset(&self, offset: TextSize) -> TokenAt {
|
||||
match self.binary_search_by_key(&offset, ruff_text_size::Ranged::start) {
|
||||
match self.binary_search_by_start(offset) {
|
||||
// The token at `index` starts exactly at `offset.
|
||||
// ```python
|
||||
// object.attribute
|
||||
@@ -649,28 +643,25 @@ impl Tokens {
|
||||
/// If the given offset is inside a token range at any point
|
||||
/// other than the start of the range.
|
||||
pub fn before(&self, offset: TextSize) -> &[Token] {
|
||||
match self.binary_search_by(|token| token.start().cmp(&offset)) {
|
||||
Ok(idx) => &self[..idx],
|
||||
Err(idx) => {
|
||||
// We can't use `saturating_sub` here because a file could contain a BOM header, in
|
||||
// which case the token starts at offset 3 for UTF-8 encoded file content.
|
||||
if idx > 0 {
|
||||
if let Some(prev) = self.get(idx - 1) {
|
||||
// If it's equal to the end offset, then it's at a token boundary which is
|
||||
// valid. If it's greater than the end offset, then it's in the gap between
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset >= prev.end(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
prev.range()
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::before_impl(&self.raw, offset)
|
||||
}
|
||||
|
||||
&self[..idx]
|
||||
}
|
||||
fn before_impl(tokens: &[Token], offset: TextSize) -> &[Token] {
|
||||
let partition_point = tokens.partition_point(|token| token.start() < offset);
|
||||
let before = &tokens[..partition_point];
|
||||
|
||||
if let Some(last) = before.last() {
|
||||
// If it's equal to the end offset, then it's at a token boundary which is
|
||||
// valid. If it's greater than the end offset, then it's in the gap between
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset >= last.end(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
last.range()
|
||||
);
|
||||
}
|
||||
before
|
||||
}
|
||||
|
||||
/// Returns a slice of tokens after the given [`TextSize`] offset.
|
||||
@@ -684,28 +675,21 @@ impl Tokens {
|
||||
/// If the given offset is inside a token range at any point
|
||||
/// other than the start of the range.
|
||||
pub fn after(&self, offset: TextSize) -> &[Token] {
|
||||
match self.binary_search_by(|token| token.start().cmp(&offset)) {
|
||||
Ok(idx) => &self[idx..],
|
||||
Err(idx) => {
|
||||
// We can't use `saturating_sub` here because a file could contain a BOM header, in
|
||||
// which case the token starts at offset 3 for UTF-8 encoded file content.
|
||||
if idx > 0 {
|
||||
if let Some(prev) = self.get(idx - 1) {
|
||||
// If it's equal to the end offset, then it's at a token boundary which is
|
||||
// valid. If it's greater than the end offset, then it's in the gap between
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset >= prev.end(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
prev.range()
|
||||
);
|
||||
}
|
||||
}
|
||||
let partition_point = self.partition_point(|token| token.end() <= offset);
|
||||
let after = &self[partition_point..];
|
||||
|
||||
&self[idx..]
|
||||
}
|
||||
if let Some(first) = after.first() {
|
||||
// valid. If it's greater than the end offset, then it's in the gap between
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset <= first.start(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
first.range()
|
||||
);
|
||||
}
|
||||
|
||||
after
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1099,7 +1083,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "End offset 6 is inside a token range 4..7")]
|
||||
#[should_panic(expected = "Offset 6 is inside a token range 4..7")]
|
||||
fn tokens_in_range_end_offset_inside_token() {
|
||||
let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
|
||||
tokens.in_range(TextRange::new(0.into(), 6.into()));
|
||||
|
||||
@@ -1539,103 +1539,9 @@ impl<'src> Parser<'src> {
|
||||
flags = flags.with_unclosed(true);
|
||||
}
|
||||
|
||||
// test_ok pep701_f_string_py312
|
||||
// # parse_options: {"target-version": "3.12"}
|
||||
// f'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// f"{'\n'.join(a)}" # escape sequence
|
||||
// f'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// f"test {a \
|
||||
// } more" # line continuation
|
||||
|
||||
// test_ok pep750_t_string_py314
|
||||
// # parse_options: {"target-version": "3.14"}
|
||||
// t'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// t"{'\n'.join(a)}" # escape sequence
|
||||
// t'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// t"{t"{t"{t"{t"{t"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// t"{t'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// t"test {a \
|
||||
// } more" # line continuation
|
||||
|
||||
// test_ok pep701_f_string_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f"outer {'# not a comment'}"
|
||||
// f'outer {x:{"# not a comment"} }'
|
||||
// f"""{f'''{f'{"# not a comment"}'}'''}"""
|
||||
// f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression"""
|
||||
// f"escape outside of \t {expr}\n"
|
||||
// f"test\"abcd"
|
||||
// f"{1:\x64}" # escapes are valid in the format spec
|
||||
// f"{1:\"d\"}" # this also means that escaped outer quotes are valid
|
||||
|
||||
// test_err pep701_f_string_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// f"{'\n'.join(a)}" # escape sequence
|
||||
// f'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// f"test {a \
|
||||
// } more" # line continuation
|
||||
// f"""{f"""{x}"""}""" # mark the whole triple quote
|
||||
// f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors
|
||||
|
||||
// test_err nested_quote_in_format_spec_py312
|
||||
// # parse_options: {"target-version": "3.12"}
|
||||
// f"{1:""}" # this is a ParseError on all versions
|
||||
|
||||
// test_ok non_nested_quote_in_format_spec_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f"{1:''}" # but this is okay on all versions
|
||||
let range = self.node_range(start);
|
||||
|
||||
if !self.options.target_version.supports_pep_701()
|
||||
&& matches!(kind, InterpolatedStringKind::FString)
|
||||
{
|
||||
let quote_bytes = flags.quote_str().as_bytes();
|
||||
let quote_len = flags.quote_len();
|
||||
for expr in elements.interpolations() {
|
||||
// We need to check the whole expression range, including any leading or trailing
|
||||
// debug text, but exclude the format spec, where escapes and escaped, reused quotes
|
||||
// are allowed.
|
||||
let range = expr
|
||||
.format_spec
|
||||
.as_ref()
|
||||
.map(|format_spec| TextRange::new(expr.start(), format_spec.start()))
|
||||
.unwrap_or(expr.range);
|
||||
for slash_position in memchr::memchr_iter(b'\\', self.source[range].as_bytes()) {
|
||||
let slash_position = TextSize::try_from(slash_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash),
|
||||
TextRange::at(range.start() + slash_position, '\\'.text_len()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(quote_position) =
|
||||
memchr::memmem::find(self.source[range].as_bytes(), quote_bytes)
|
||||
{
|
||||
let quote_position = TextSize::try_from(quote_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote),
|
||||
TextRange::at(range.start() + quote_position, quote_len),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_fstring_comments(range);
|
||||
}
|
||||
|
||||
InterpolatedStringData {
|
||||
elements,
|
||||
range,
|
||||
range: self.node_range(start),
|
||||
flags,
|
||||
}
|
||||
}
|
||||
@@ -1921,12 +1827,110 @@ impl<'src> Parser<'src> {
|
||||
);
|
||||
}
|
||||
|
||||
// test_ok pep701_f_string_py312
|
||||
// # parse_options: {"target-version": "3.12"}
|
||||
// f'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// f"{'\n'.join(a)}" # escape sequence
|
||||
// f'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// f"test {a \
|
||||
// } more" # line continuation
|
||||
|
||||
// test_ok pep750_t_string_py314
|
||||
// # parse_options: {"target-version": "3.14"}
|
||||
// t'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// t"{'\n'.join(a)}" # escape sequence
|
||||
// t'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// t"{t"{t"{t"{t"{t"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// t"{t'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// t"test {a \
|
||||
// } more" # line continuation
|
||||
|
||||
// test_ok pep701_f_string_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f"outer {'# not a comment'}"
|
||||
// f'outer {x:{"# not a comment"} }'
|
||||
// f"""{f'''{f'{"# not a comment"}'}'''}"""
|
||||
// f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression"""
|
||||
// f"escape outside of \t {expr}\n"
|
||||
// f"test\"abcd"
|
||||
// f"{1:\x64}" # escapes are valid in the format spec
|
||||
// f"{1:\"d\"}" # this also means that escaped outer quotes are valid
|
||||
|
||||
// test_err pep701_f_string_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f'Magic wand: { bag['wand'] }' # nested quotes
|
||||
// f"{'\n'.join(a)}" # escape sequence
|
||||
// f'''A complex trick: {
|
||||
// bag['bag'] # comment
|
||||
// }'''
|
||||
// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting
|
||||
// f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes
|
||||
// f"test {a \
|
||||
// } more" # line continuation
|
||||
// f"""{f"""{x}"""}""" # mark the whole triple quote
|
||||
// f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors
|
||||
|
||||
// test_err pep701_nested_interpolation_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// # nested interpolations also need to be checked
|
||||
// f'{1: abcd "{'aa'}" }'
|
||||
// f'{1: abcd "{"\n"}" }'
|
||||
|
||||
// test_err nested_quote_in_format_spec_py312
|
||||
// # parse_options: {"target-version": "3.12"}
|
||||
// f"{1:""}" # this is a ParseError on all versions
|
||||
|
||||
// test_ok non_nested_quote_in_format_spec_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f"{1:''}" # but this is okay on all versions
|
||||
let range = self.node_range(start);
|
||||
|
||||
if !self.options.target_version.supports_pep_701()
|
||||
&& matches!(string_kind, InterpolatedStringKind::FString)
|
||||
{
|
||||
// We need to check the whole expression range, including any leading or trailing
|
||||
// debug text, but exclude the format spec, where escapes and escaped, reused quotes
|
||||
// are allowed.
|
||||
let range = format_spec
|
||||
.as_ref()
|
||||
.map(|format_spec| TextRange::new(range.start(), format_spec.start()))
|
||||
.unwrap_or(range);
|
||||
|
||||
let quote_bytes = flags.quote_str().as_bytes();
|
||||
let quote_len = flags.quote_len();
|
||||
for slash_position in memchr::memchr_iter(b'\\', self.source[range].as_bytes()) {
|
||||
let slash_position = TextSize::try_from(slash_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash),
|
||||
TextRange::at(range.start() + slash_position, '\\'.text_len()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(quote_position) =
|
||||
memchr::memmem::find(self.source[range].as_bytes(), quote_bytes)
|
||||
{
|
||||
let quote_position = TextSize::try_from(quote_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote),
|
||||
TextRange::at(range.start() + quote_position, quote_len),
|
||||
);
|
||||
}
|
||||
|
||||
self.check_fstring_comments(range);
|
||||
}
|
||||
|
||||
ast::InterpolatedElement {
|
||||
expression: Box::new(value.expr),
|
||||
debug_text,
|
||||
conversion,
|
||||
format_spec,
|
||||
range: self.node_range(start),
|
||||
range,
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1137,6 +1137,9 @@ impl Display for SemanticSyntaxError {
|
||||
}
|
||||
SemanticSyntaxErrorKind::BreakOutsideLoop => f.write_str("`break` outside loop"),
|
||||
SemanticSyntaxErrorKind::ContinueOutsideLoop => f.write_str("`continue` outside loop"),
|
||||
SemanticSyntaxErrorKind::DifferentMatchPatternBindings => {
|
||||
write!(f, "alternative patterns bind different names")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1516,6 +1519,20 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents the use of a `continue` statement outside of a loop.
|
||||
ContinueOutsideLoop,
|
||||
|
||||
/// Represents the use of alternative patterns in a `match` statement that bind different names.
|
||||
///
|
||||
/// Python requires all alternatives in an OR pattern (`|`) to bind the same set of names.
|
||||
/// Using different names results in a `SyntaxError`.
|
||||
///
|
||||
/// ## Example:
|
||||
///
|
||||
/// ```python
|
||||
/// match 5:
|
||||
/// case [x] | [y]: # error
|
||||
/// ...
|
||||
/// ```
|
||||
DifferentMatchPatternBindings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
@@ -1758,7 +1775,9 @@ impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> {
|
||||
self.insert(name);
|
||||
}
|
||||
}
|
||||
Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => {
|
||||
Pattern::MatchOr(ast::PatternMatchOr {
|
||||
patterns, range, ..
|
||||
}) => {
|
||||
// each of these patterns should be visited separately because patterns can only be
|
||||
// duplicated within a single arm of the or pattern. For example, the case below is
|
||||
// a valid pattern.
|
||||
@@ -1766,12 +1785,48 @@ impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> {
|
||||
// test_ok multiple_assignment_in_case_pattern
|
||||
// match 2:
|
||||
// case Class(x) | [x] | x: ...
|
||||
|
||||
let mut previous_names: Option<FxHashSet<&ast::name::Name>> = None;
|
||||
for pattern in patterns {
|
||||
let mut visitor = Self {
|
||||
names: FxHashSet::default(),
|
||||
ctx: self.ctx,
|
||||
};
|
||||
visitor.visit_pattern(pattern);
|
||||
let Some(prev) = &previous_names else {
|
||||
previous_names = Some(visitor.names);
|
||||
continue;
|
||||
};
|
||||
if prev.symmetric_difference(&visitor.names).next().is_some() {
|
||||
// test_err different_match_pattern_bindings
|
||||
// match x:
|
||||
// case [a] | [b]: ...
|
||||
// case [a] | []: ...
|
||||
// case (x, y) | (x,): ...
|
||||
// case [a, _] | [a, b]: ...
|
||||
// case (x, (y | z)): ...
|
||||
// case [a] | [b] | [c]: ...
|
||||
// case [] | [a]: ...
|
||||
// case [a] | [C(x)]: ...
|
||||
// case [[a] | [b]]: ...
|
||||
// case [C(a)] | [C(b)]: ...
|
||||
// case [C(D(a))] | [C(D(b))]: ...
|
||||
// case [(a, b)] | [(c, d)]: ...
|
||||
|
||||
// test_ok different_match_pattern_bindings
|
||||
// match x:
|
||||
// case [a] | [a]: ...
|
||||
// case (x, y) | (x, y): ...
|
||||
// case (x, (y | y)): ...
|
||||
// case [a, _] | [a, _]: ...
|
||||
// case [a] | [C(a)]: ...
|
||||
SemanticSyntaxChecker::add_error(
|
||||
self.ctx,
|
||||
SemanticSyntaxErrorKind::DifferentMatchPatternBindings,
|
||||
*range,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -427,3 +427,12 @@ Module(
|
||||
| ^^^ Syntax Error: name capture `var` makes remaining patterns unreachable
|
||||
12 | case 2: ...
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
9 | case 2: ...
|
||||
10 | match x:
|
||||
11 | case enum.variant | var: ... # or pattern with irrefutable part
|
||||
| ^^^^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names
|
||||
12 | case 2: ...
|
||||
|
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/pep701_nested_interpolation_py311.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..138,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 92..114,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 92..114,
|
||||
value: FStringValue {
|
||||
inner: Single(
|
||||
FString(
|
||||
FString {
|
||||
range: 92..114,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 94..113,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 95..96,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 97..112,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 97..104,
|
||||
node_index: NodeIndex(None),
|
||||
value: " abcd \"",
|
||||
},
|
||||
),
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 104..110,
|
||||
node_index: NodeIndex(None),
|
||||
expression: StringLiteral(
|
||||
ExprStringLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 105..109,
|
||||
value: StringLiteralValue {
|
||||
inner: Single(
|
||||
StringLiteral {
|
||||
range: 105..109,
|
||||
node_index: NodeIndex(None),
|
||||
value: "aa",
|
||||
flags: StringLiteralFlags {
|
||||
quote_style: Single,
|
||||
prefix: Empty,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
),
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 110..112,
|
||||
node_index: NodeIndex(None),
|
||||
value: "\" ",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 115..137,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 115..137,
|
||||
value: FStringValue {
|
||||
inner: Single(
|
||||
FString(
|
||||
FString {
|
||||
range: 115..137,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 117..136,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 118..119,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 120..135,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 120..127,
|
||||
node_index: NodeIndex(None),
|
||||
value: " abcd \"",
|
||||
},
|
||||
),
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 127..133,
|
||||
node_index: NodeIndex(None),
|
||||
expression: StringLiteral(
|
||||
ExprStringLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 128..132,
|
||||
value: StringLiteralValue {
|
||||
inner: Single(
|
||||
StringLiteral {
|
||||
range: 128..132,
|
||||
node_index: NodeIndex(None),
|
||||
value: "\n",
|
||||
flags: StringLiteralFlags {
|
||||
quote_style: Double,
|
||||
prefix: Empty,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
),
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 133..135,
|
||||
node_index: NodeIndex(None),
|
||||
value: "\" ",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Unsupported Syntax Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: {"target-version": "3.11"}
|
||||
2 | # nested interpolations also need to be checked
|
||||
3 | f'{1: abcd "{'aa'}" }'
|
||||
| ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12)
|
||||
4 | f'{1: abcd "{"\n"}" }'
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
2 | # nested interpolations also need to be checked
|
||||
3 | f'{1: abcd "{'aa'}" }'
|
||||
4 | f'{1: abcd "{"\n"}" }'
|
||||
| ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12)
|
||||
|
|
||||
@@ -613,3 +613,25 @@ Module(
|
||||
| ^^ Syntax Error: Star pattern cannot be used here
|
||||
24 | pass
|
||||
|
|
||||
|
||||
|
||||
## Semantic Syntax Errors
|
||||
|
||||
|
|
||||
7 | case *foo:
|
||||
8 | pass
|
||||
9 | case *foo | 1:
|
||||
| ^^^^^^^^ Syntax Error: alternative patterns bind different names
|
||||
10 | pass
|
||||
11 | case 1 | *foo:
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
9 | case *foo | 1:
|
||||
10 | pass
|
||||
11 | case 1 | *foo:
|
||||
| ^^^^^^^^ Syntax Error: alternative patterns bind different names
|
||||
12 | pass
|
||||
13 | case Foo(*_):
|
||||
|
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..147,
|
||||
body: [
|
||||
Match(
|
||||
StmtMatch {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..146,
|
||||
subject: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 6..7,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
cases: [
|
||||
MatchCase {
|
||||
range: 13..32,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: MatchOr(
|
||||
PatternMatchOr {
|
||||
range: 18..27,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 18..21,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 19..20,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 19..20,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 24..27,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 25..26,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 25..26,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 29..32,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 29..32,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
MatchCase {
|
||||
range: 37..62,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: MatchOr(
|
||||
PatternMatchOr {
|
||||
range: 42..57,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 42..48,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 43..44,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 43..44,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 46..47,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 46..47,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 51..57,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 52..53,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 52..53,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 55..56,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 55..56,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 59..62,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 59..62,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
MatchCase {
|
||||
range: 67..89,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 72..84,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 73..74,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 73..74,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchOr(
|
||||
PatternMatchOr {
|
||||
range: 77..82,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 77..78,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 77..78,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 81..82,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 81..82,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 86..89,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 86..89,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
MatchCase {
|
||||
range: 94..119,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: MatchOr(
|
||||
PatternMatchOr {
|
||||
range: 99..114,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 99..105,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 100..101,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 100..101,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 103..104,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: None,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 108..114,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 109..110,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 109..110,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 112..113,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: None,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 116..119,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 116..119,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
MatchCase {
|
||||
range: 124..146,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: MatchOr(
|
||||
PatternMatchOr {
|
||||
range: 129..141,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 129..132,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 130..131,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 130..131,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
MatchSequence(
|
||||
PatternMatchSequence {
|
||||
range: 135..141,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchClass(
|
||||
PatternMatchClass {
|
||||
range: 136..140,
|
||||
node_index: NodeIndex(None),
|
||||
cls: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 136..137,
|
||||
id: Name("C"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
arguments: PatternArguments {
|
||||
range: 137..140,
|
||||
node_index: NodeIndex(None),
|
||||
patterns: [
|
||||
MatchAs(
|
||||
PatternMatchAs {
|
||||
range: 138..139,
|
||||
node_index: NodeIndex(None),
|
||||
pattern: None,
|
||||
name: Some(
|
||||
Identifier {
|
||||
id: Name("a"),
|
||||
range: 138..139,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
keywords: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 143..146,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 143..146,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
@@ -274,15 +274,11 @@ impl<'de> serde::de::Deserialize<'de> for NameImports {
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for NameImports {
|
||||
fn schema_name() -> String {
|
||||
"NameImports".to_string()
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("NameImports")
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
schemars::json_schema!({ "type": "string" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
//! bindings to the Workspace API
|
||||
|
||||
use crate::{TextRange, TextSize};
|
||||
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::Schema};
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl JsonSchema for TextSize {
|
||||
fn schema_name() -> String {
|
||||
String::from("TextSize")
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
Cow::Borrowed("TextSize")
|
||||
}
|
||||
|
||||
fn json_schema(r#gen: &mut SchemaGenerator) -> Schema {
|
||||
@@ -21,8 +22,8 @@ impl JsonSchema for TextSize {
|
||||
}
|
||||
|
||||
impl JsonSchema for TextRange {
|
||||
fn schema_name() -> String {
|
||||
String::from("TextRange")
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
Cow::Borrowed("TextRange")
|
||||
}
|
||||
|
||||
fn json_schema(r#gen: &mut SchemaGenerator) -> Schema {
|
||||
|
||||
@@ -42,6 +42,7 @@ regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
shellexpand = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
@@ -63,6 +64,7 @@ ignored = ["colored"]
|
||||
default = []
|
||||
schemars = [
|
||||
"dep:schemars",
|
||||
"dep:serde_json",
|
||||
"ruff_formatter/schemars",
|
||||
"ruff_linter/schemars",
|
||||
"ruff_python_formatter/schemars",
|
||||
|
||||
@@ -469,6 +469,7 @@ pub struct Options {
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case"
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", schemars(!from))]
|
||||
pub struct LintOptions {
|
||||
#[serde(flatten)]
|
||||
pub common: LintCommonOptions,
|
||||
@@ -563,8 +564,8 @@ impl OptionsMetadata for DeprecatedTopLevelLintOptions {
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for DeprecatedTopLevelLintOptions {
|
||||
fn schema_name() -> String {
|
||||
"DeprecatedTopLevelLintOptions".to_owned()
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("DeprecatedTopLevelLintOptions")
|
||||
}
|
||||
fn schema_id() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed(concat!(
|
||||
@@ -573,28 +574,25 @@ impl schemars::JsonSchema for DeprecatedTopLevelLintOptions {
|
||||
"DeprecatedTopLevelLintOptions"
|
||||
))
|
||||
}
|
||||
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
use schemars::schema::Schema;
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
use serde_json::Value;
|
||||
|
||||
let common_schema = LintCommonOptions::json_schema(generator);
|
||||
let mut schema_obj = common_schema.into_object();
|
||||
|
||||
if let Some(object) = schema_obj.object.as_mut() {
|
||||
for property in object.properties.values_mut() {
|
||||
if let Schema::Object(property_object) = property {
|
||||
if let Some(metadata) = &mut property_object.metadata {
|
||||
metadata.deprecated = true;
|
||||
} else {
|
||||
property_object.metadata = Some(Box::new(schemars::schema::Metadata {
|
||||
deprecated: true,
|
||||
..schemars::schema::Metadata::default()
|
||||
}));
|
||||
}
|
||||
let mut schema = LintCommonOptions::json_schema(generator);
|
||||
if let Some(properties) = schema
|
||||
.ensure_object()
|
||||
.get_mut("properties")
|
||||
.and_then(|value| value.as_object_mut())
|
||||
{
|
||||
for property in properties.values_mut() {
|
||||
if let Ok(property_schema) = <&mut schemars::Schema>::try_from(property) {
|
||||
property_schema
|
||||
.ensure_object()
|
||||
.insert("deprecated".to_string(), Value::Bool(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Schema::Object(schema_obj)
|
||||
schema
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
194
crates/ty/docs/rules.md
generated
194
crates/ty/docs/rules.md
generated
@@ -39,7 +39,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ f(int) # error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ a = 1
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class B(A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L302" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L303" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L323" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L324" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L527" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L528" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L551" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L552" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L355" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L356" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -474,7 +474,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L597" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L598" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L638" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ a: int = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1748" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L659" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L660" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L690" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L740" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L741" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -650,7 +650,7 @@ with 1:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L761" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L762" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -679,7 +679,7 @@ a: str
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L784" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L785" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -723,7 +723,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L820" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L821" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -756,7 +756,7 @@ class C[U](Generic[T]): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L573" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ alice["height"] # KeyError: 'height'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -822,7 +822,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -856,7 +856,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L501" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L502" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -888,7 +888,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L922" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -938,7 +938,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1022" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -964,7 +964,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L437" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L438" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -998,7 +998,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1041" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1042" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1047,7 +1047,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L618" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L619" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1072,7 +1072,7 @@ def func() -> int:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1084" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1130,7 +1130,7 @@ TODO #14889
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L875" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1157,7 +1157,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1123" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1124" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1187,7 +1187,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1147" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1217,7 +1217,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1199" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1251,7 +1251,7 @@ f(10) # Error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1285,7 +1285,7 @@ class C:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1227" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1228" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1320,7 +1320,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1257" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1345,7 +1345,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1849" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1850" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1378,7 +1378,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1275" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1407,7 +1407,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1298" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1299" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1431,7 +1431,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1316" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1317" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1457,7 +1457,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1367" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1368" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1484,7 +1484,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1602" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1603" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1542,7 +1542,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1724" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1725" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1572,7 +1572,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1458" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1459" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1601,7 +1601,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1503" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1504" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1628,7 +1628,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1481" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1482" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1656,7 +1656,7 @@ def _(x: int):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1524" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1525" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1702,7 +1702,7 @@ class A:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1581" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1582" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1729,7 +1729,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1624" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1757,7 +1757,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1645" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1646" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1782,7 +1782,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1665" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1807,7 +1807,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1336" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1337" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1844,7 +1844,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1683" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1684" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1872,7 +1872,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1705" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1706" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1897,7 +1897,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L466" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L467" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1938,7 +1938,7 @@ class SubProto(BaseProto, Protocol):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L281" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1959,6 +1959,37 @@ def old_func(): ...
|
||||
old_func() # emits [deprecated] diagnostic
|
||||
```
|
||||
|
||||
## `ignore-comment-unknown-rule`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ignore-comment-unknown-rule" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L40" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for `ty: ignore[code]` where `code` isn't a known lint rule.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
A `ty: ignore[code]` directive with a `code` that doesn't match
|
||||
any known rule will not suppress any type errors, and is probably a mistake.
|
||||
|
||||
**Examples**
|
||||
|
||||
```py
|
||||
a = 20 / 0 # ty: ignore[division-by-zer]
|
||||
```
|
||||
|
||||
Use instead:
|
||||
|
||||
```py
|
||||
a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
```
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
|
||||
<small>
|
||||
@@ -1995,7 +2026,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1388" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2023,7 +2054,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2055,7 +2086,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1410" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2087,7 +2118,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1776" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1777" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2114,7 +2145,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2132,44 +2163,13 @@ Using `reveal_type` without importing it will raise a `NameError` at runtime.
|
||||
reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
```
|
||||
|
||||
## `unknown-rule`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-rule" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L40" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for `ty: ignore[code]` where `code` isn't a known lint rule.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
A `ty: ignore[code]` directive with a `code` that doesn't match
|
||||
any known rule will not suppress any type errors, and is probably a mistake.
|
||||
|
||||
**Examples**
|
||||
|
||||
```py
|
||||
a = 20 / 0 # ty: ignore[division-by-zer]
|
||||
```
|
||||
|
||||
Use instead:
|
||||
|
||||
```py
|
||||
a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
```
|
||||
|
||||
## `unresolved-global`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1797" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1798" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2227,7 +2227,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2266,7 +2266,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L965" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2329,7 +2329,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L263" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2353,7 +2353,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1436" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1437" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-attribute]: Type `<module 'sys'>` has no attribute `last_exc`
|
||||
error[unresolved-attribute]: Module `sys` has no member `last_exc`
|
||||
--> test.py:5:7
|
||||
|
|
||||
4 | # Access `sys.last_exc` that was only added in Python 3.12
|
||||
@@ -962,7 +962,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-attribute]: Type `<module 'os'>` has no attribute `grantpt`
|
||||
error[unresolved-attribute]: Module `os` has no member `grantpt`
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | import os
|
||||
|
||||
@@ -250,11 +250,11 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
|
||||
("test.py", "print(10)"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning[unknown-rule]: Unknown lint rule `division-by-zer`
|
||||
warning[unknown-rule]: Unknown rule `division-by-zer`. Did you mean `division-by-zero`?
|
||||
--> pyproject.toml:3:1
|
||||
|
|
||||
2 | [tool.ty.rules]
|
||||
@@ -265,7 +265,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -275,16 +275,16 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
|
||||
fn cli_unknown_rules() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_file("test.py", "print(10)")?;
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r###"
|
||||
assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning[unknown-rule]: Unknown lint rule `division-by-zer`
|
||||
warning[unknown-rule]: Unknown rule `division-by-zer`. Did you mean `division-by-zero`?
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -852,7 +852,7 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -864,7 +864,7 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `division-by-zero` was selected in the configuration file
|
||||
|
||||
warning[unknown-rule]: Unknown lint rule `division-by-zer`
|
||||
warning[unknown-rule]: Unknown rule `division-by-zer`. Did you mean `division-by-zero`?
|
||||
--> pyproject.toml:10:1
|
||||
|
|
||||
8 | [tool.ty.overrides.rules]
|
||||
@@ -884,7 +884,7 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
|
||||
Found 3 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1112,11 +1112,11 @@ print(sys.last_exc, os.getegid())
|
||||
assert_eq!(diagnostics.len(), 2);
|
||||
assert_eq!(
|
||||
diagnostics[0].primary_message(),
|
||||
"Type `<module 'sys'>` has no attribute `last_exc`"
|
||||
"Module `sys` has no member `last_exc`"
|
||||
);
|
||||
assert_eq!(
|
||||
diagnostics[1].primary_message(),
|
||||
"Type `<module 'os'>` has no attribute `getegid`"
|
||||
"Module `os` has no member `getegid`"
|
||||
);
|
||||
|
||||
// Change the python version
|
||||
|
||||
@@ -608,7 +608,7 @@ struct ScopedTarget<'t> {
|
||||
node: ast::AnyNodeRef<'t>,
|
||||
}
|
||||
|
||||
/// Returns a slice of tokens that all start before or at the given
|
||||
/// Returns a slice of tokens that all start before the given
|
||||
/// [`TextSize`] offset.
|
||||
///
|
||||
/// If the given offset is between two tokens, the returned slice will end just
|
||||
@@ -620,11 +620,9 @@ struct ScopedTarget<'t> {
|
||||
/// range (including if it's at the very beginning), then that token will be
|
||||
/// included in the slice returned.
|
||||
fn tokens_start_before(tokens: &Tokens, offset: TextSize) -> &[Token] {
|
||||
let idx = match tokens.binary_search_by(|token| token.start().cmp(&offset)) {
|
||||
Ok(idx) => idx,
|
||||
Err(idx) => idx,
|
||||
};
|
||||
&tokens[..idx]
|
||||
let partition_point = tokens.partition_point(|token| token.start() < offset);
|
||||
|
||||
&tokens[..partition_point]
|
||||
}
|
||||
|
||||
/// Returns a suffix of `tokens` corresponding to the `kinds` given.
|
||||
@@ -1453,6 +1451,21 @@ def frob(): ...
|
||||
");
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ty/issues/1392>
|
||||
///
|
||||
/// This test ensures completions work when the cursor is at the
|
||||
/// start of a zero-length token.
|
||||
#[test]
|
||||
fn completion_at_eof() {
|
||||
let test = cursor_test("def f(msg: str):\n msg.<CURSOR>");
|
||||
test.assert_completions_include("upper");
|
||||
test.assert_completions_include("capitalize");
|
||||
|
||||
let test = cursor_test("def f(msg: str):\n msg.u<CURSOR>");
|
||||
test.assert_completions_include("upper");
|
||||
test.assert_completions_do_not_include("capitalize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_comprehension1() {
|
||||
let test = cursor_test(
|
||||
@@ -3896,6 +3909,55 @@ print(t'''{Foo} and Foo.zqzq<CURSOR>
|
||||
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typevar_with_upper_bound() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
def f[T: str](msg: T):
|
||||
msg.<CURSOR>
|
||||
",
|
||||
);
|
||||
test.assert_completions_include("upper");
|
||||
test.assert_completions_include("capitalize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typevar_with_constraints() {
|
||||
// Test TypeVar with constraints
|
||||
let test = cursor_test(
|
||||
"\
|
||||
from typing import TypeVar
|
||||
|
||||
class A:
|
||||
only_on_a: int
|
||||
on_a_and_b: str
|
||||
|
||||
class B:
|
||||
only_on_b: float
|
||||
on_a_and_b: str
|
||||
|
||||
T = TypeVar('T', A, B)
|
||||
|
||||
def f(x: T):
|
||||
x.<CURSOR>
|
||||
",
|
||||
);
|
||||
test.assert_completions_include("on_a_and_b");
|
||||
test.assert_completions_do_not_include("only_on_a");
|
||||
test.assert_completions_do_not_include("only_on_b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typevar_without_bounds_or_constraints() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
def f[T](x: T):
|
||||
x.<CURSOR>
|
||||
",
|
||||
);
|
||||
test.assert_completions_include("__repr__");
|
||||
}
|
||||
|
||||
// NOTE: The methods below are getting somewhat ridiculous.
|
||||
// We should refactor this by converting to using a builder
|
||||
// to set different modes. ---AG
|
||||
|
||||
@@ -284,13 +284,13 @@ impl GotoTarget<'_> {
|
||||
// When asking the type of a callable, usually you want the callable itself?
|
||||
// (i.e. the type of `MyClass` in `MyClass()` is `<class MyClass>` and not `() -> MyClass`)
|
||||
GotoTarget::Call { callable, .. } => callable.inferred_type(model),
|
||||
GotoTarget::TypeParamTypeVarName(typevar) => typevar.inferred_type(model),
|
||||
// TODO: Support identifier targets
|
||||
GotoTarget::PatternMatchRest(_)
|
||||
| GotoTarget::PatternKeywordArgument(_)
|
||||
| GotoTarget::PatternMatchStarName(_)
|
||||
| GotoTarget::PatternMatchAsName(_)
|
||||
| GotoTarget::ImportModuleComponent { .. }
|
||||
| GotoTarget::TypeParamTypeVarName(_)
|
||||
| GotoTarget::TypeParamParamSpecName(_)
|
||||
| GotoTarget::TypeParamTypeVarTupleName(_)
|
||||
| GotoTarget::NonLocal { .. }
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::{KnownInstanceType, Type, TypeVarVariance};
|
||||
use ty_python_semantic::{DisplaySettings, SemanticModel};
|
||||
|
||||
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
|
||||
@@ -20,7 +20,7 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
|
||||
}
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let ty = goto_target.inferred_type(&model).map(HoverContent::Type);
|
||||
let ty = goto_target.inferred_type(&model);
|
||||
let docs = goto_target
|
||||
.get_definition_targets(
|
||||
file,
|
||||
@@ -30,13 +30,20 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
|
||||
.and_then(|definitions| definitions.docstring(db))
|
||||
.map(HoverContent::Docstring);
|
||||
|
||||
if let Some(HoverContent::Type(ty)) = ty {
|
||||
tracing::debug!("Inferred type of covering node is {}", ty.display(db));
|
||||
}
|
||||
|
||||
// TODO: Render the symbol's signature instead of just its type.
|
||||
let mut contents = Vec::new();
|
||||
contents.extend(ty);
|
||||
if let Some(ty) = ty {
|
||||
tracing::debug!("Inferred type of covering node is {}", ty.display(db));
|
||||
contents.push(match ty {
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => typevar
|
||||
.bind_pep695(db)
|
||||
.map_or(HoverContent::Type(ty, None), |typevar| {
|
||||
HoverContent::Type(Type::TypeVar(typevar), Some(typevar.variance(db)))
|
||||
}),
|
||||
Type::TypeVar(typevar) => HoverContent::Type(ty, Some(typevar.variance(db))),
|
||||
_ => HoverContent::Type(ty, None),
|
||||
});
|
||||
}
|
||||
contents.extend(docs);
|
||||
|
||||
if contents.is_empty() {
|
||||
@@ -110,7 +117,7 @@ impl fmt::Display for DisplayHover<'_> {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum HoverContent<'db> {
|
||||
Type(Type<'db>),
|
||||
Type(Type<'db>, Option<TypeVarVariance>),
|
||||
Docstring(Docstring),
|
||||
}
|
||||
|
||||
@@ -133,13 +140,24 @@ pub(crate) struct DisplayHoverContent<'a, 'db> {
|
||||
impl fmt::Display for DisplayHoverContent<'_, '_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.content {
|
||||
HoverContent::Type(ty) => self
|
||||
.kind
|
||||
.fenced_code_block(
|
||||
ty.display_with(self.db, DisplaySettings::default().multiline()),
|
||||
"python",
|
||||
)
|
||||
.fmt(f),
|
||||
HoverContent::Type(ty, variance) => {
|
||||
let variance = match variance {
|
||||
Some(TypeVarVariance::Covariant) => " (covariant)",
|
||||
Some(TypeVarVariance::Contravariant) => " (contravariant)",
|
||||
Some(TypeVarVariance::Invariant) => " (invariant)",
|
||||
Some(TypeVarVariance::Bivariant) => " (bivariant)",
|
||||
None => "",
|
||||
};
|
||||
self.kind
|
||||
.fenced_code_block(
|
||||
format!(
|
||||
"{}{variance}",
|
||||
ty.display_with(self.db, DisplaySettings::default().multiline())
|
||||
),
|
||||
"python",
|
||||
)
|
||||
.fmt(f)
|
||||
}
|
||||
HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
|
||||
}
|
||||
}
|
||||
@@ -1590,10 +1608,10 @@ def ab(a: int, *, c: int):
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Alias
|
||||
T@Alias (invariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Alias
|
||||
T@Alias (invariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
@@ -2058,6 +2076,444 @@ def ab(a: int, *, c: int):
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_class_typevar_variance() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Covariant[T<CURSOR>]:
|
||||
def get(self) -> T:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Covariant (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Covariant (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:17
|
||||
|
|
||||
2 | class Covariant[T]:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | def get(self) -> T:
|
||||
4 | raise ValueError
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Covariant[T]:
|
||||
def get(self) -> T<CURSOR>:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Covariant (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Covariant (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:3:22
|
||||
|
|
||||
2 | class Covariant[T]:
|
||||
3 | def get(self) -> T:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
4 | raise ValueError
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Contravariant[T<CURSOR>]:
|
||||
def set(self, x: T):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Contravariant (contravariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Contravariant (contravariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:21
|
||||
|
|
||||
2 | class Contravariant[T]:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | def set(self, x: T):
|
||||
4 | pass
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Contravariant[T]:
|
||||
def set(self, x: T<CURSOR>):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Contravariant (contravariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Contravariant (contravariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:3:22
|
||||
|
|
||||
2 | class Contravariant[T]:
|
||||
3 | def set(self, x: T):
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
4 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_function_typevar_variance() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def covariant[T<CURSOR>]() -> T:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@covariant (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@covariant (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:15
|
||||
|
|
||||
2 | def covariant[T]() -> T:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | raise ValueError
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def covariant[T]() -> T<CURSOR>:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@covariant (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@covariant (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:23
|
||||
|
|
||||
2 | def covariant[T]() -> T:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | raise ValueError
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def contravariant[T<CURSOR>](x: T):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@contravariant (contravariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@contravariant (contravariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:19
|
||||
|
|
||||
2 | def contravariant[T](x: T):
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | pass
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def contravariant[T](x: T<CURSOR>):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@contravariant (contravariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@contravariant (contravariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:25
|
||||
|
|
||||
2 | def contravariant[T](x: T):
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_type_alias_typevar_variance() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type List[T<CURSOR>] = list[T]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@List (invariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@List (invariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:11
|
||||
|
|
||||
2 | type List[T] = list[T]
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type List[T] = list[T<CURSOR>]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@List (invariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@List (invariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:21
|
||||
|
|
||||
2 | type List[T] = list[T]
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Tuple[T<CURSOR>] = tuple[T]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Tuple (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Tuple (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | type Tuple[T] = tuple[T]
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Tuple[T] = tuple[T<CURSOR>]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@Tuple (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@Tuple (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:23
|
||||
|
|
||||
2 | type Tuple[T] = tuple[T]
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_legacy_typevar_variance() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import TypeVar
|
||||
|
||||
T<CURSOR> = TypeVar('T', covariant=True)
|
||||
|
||||
def covariant() -> T:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
typing.TypeVar
|
||||
---------------------------------------------
|
||||
```python
|
||||
typing.TypeVar
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import TypeVar
|
||||
3 |
|
||||
4 | T = TypeVar('T', covariant=True)
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
5 |
|
||||
6 | def covariant() -> T:
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar('T', covariant=True)
|
||||
|
||||
def covariant() -> T<CURSOR>:
|
||||
raise ValueError
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@covariant (covariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@covariant (covariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:6:20
|
||||
|
|
||||
4 | T = TypeVar('T', covariant=True)
|
||||
5 |
|
||||
6 | def covariant() -> T:
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
7 | raise ValueError
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import TypeVar
|
||||
|
||||
T<CURSOR> = TypeVar('T', contravariant=True)
|
||||
|
||||
def contravariant(x: T):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
typing.TypeVar
|
||||
---------------------------------------------
|
||||
```python
|
||||
typing.TypeVar
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import TypeVar
|
||||
3 |
|
||||
4 | T = TypeVar('T', contravariant=True)
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
5 |
|
||||
6 | def contravariant(x: T):
|
||||
|
|
||||
");
|
||||
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar('T', contravariant=True)
|
||||
|
||||
def contravariant(x: T<CURSOR>):
|
||||
pass
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
T@contravariant (contravariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
T@contravariant (contravariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:6:22
|
||||
|
|
||||
4 | T = TypeVar('T', contravariant=True)
|
||||
5 |
|
||||
6 | def contravariant(x: T):
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
7 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn hover(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -145,8 +145,12 @@ impl<'a> Importer<'a> {
|
||||
let request = request.avoid_conflicts(self.db, self.file, members);
|
||||
let mut symbol_text: Box<str> = request.member.into();
|
||||
let Some(response) = self.find(&request, members.at) else {
|
||||
let import = Insertion::start_of_file(self.parsed.suite(), self.source, self.stylist)
|
||||
.into_edit(&request.to_string());
|
||||
let insertion = if let Some(future) = self.find_last_future_import() {
|
||||
Insertion::end_of_statement(future.stmt, self.source, self.stylist)
|
||||
} else {
|
||||
Insertion::start_of_file(self.parsed.suite(), self.source, self.stylist)
|
||||
};
|
||||
let import = insertion.into_edit(&request.to_string());
|
||||
if matches!(request.style, ImportStyle::Import) {
|
||||
symbol_text = format!("{}.{}", request.module, request.member).into();
|
||||
}
|
||||
@@ -241,6 +245,19 @@ impl<'a> Importer<'a> {
|
||||
}
|
||||
choice
|
||||
}
|
||||
|
||||
/// Find the last `from __future__` import statement in the AST.
|
||||
fn find_last_future_import(&self) -> Option<&'a AstImport> {
|
||||
self.imports
|
||||
.iter()
|
||||
.take_while(|import| {
|
||||
import
|
||||
.stmt
|
||||
.as_import_from_stmt()
|
||||
.is_some_and(|import_from| import_from.module.as_deref() == Some("__future__"))
|
||||
})
|
||||
.last()
|
||||
}
|
||||
}
|
||||
|
||||
/// A map of symbols in scope at a particular location in a module.
|
||||
@@ -1293,6 +1310,44 @@ def foo():
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_future_import() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
from __future__ import annotations
|
||||
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.import("typing", "TypeVar"), @r"
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
|
||||
typing.TypeVar
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_future_import_after_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
"This is a module level docstring"
|
||||
from __future__ import annotations
|
||||
|
||||
<CURSOR>
|
||||
"#,
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.import("typing", "TypeVar"), @r#"
|
||||
"This is a module level docstring"
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
|
||||
typing.TypeVar
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualify_symbol_to_avoid_overwriting_other_symbol_in_scope() {
|
||||
let test = cursor_test(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::Db;
|
||||
use bitflags::bitflags;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
@@ -467,13 +468,6 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
self.classify_from_type_and_name_str(ty, local_name.id.as_str())
|
||||
}
|
||||
|
||||
fn visit_type_annotation(&mut self, annotation: &ast::Expr) {
|
||||
let prev_in_type_annotation = self.in_type_annotation;
|
||||
self.in_type_annotation = true;
|
||||
self.visit_expr(annotation);
|
||||
self.in_type_annotation = prev_in_type_annotation;
|
||||
}
|
||||
|
||||
// Visit parameters for a function or lambda expression and classify
|
||||
// them as parameters, selfParameter, or clsParameter as appropriate.
|
||||
fn visit_parameters(
|
||||
@@ -483,7 +477,20 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
) {
|
||||
let mut param_index = 0;
|
||||
|
||||
for any_param in parameters {
|
||||
// The `parameters.iter` method does return the parameters in sorted order but only if
|
||||
// the AST is well-formed, but e.g. not for:
|
||||
// ```py
|
||||
// def foo(self, **key, value):
|
||||
// return
|
||||
// ```
|
||||
// Ideally, the ast would use a single vec for all parameters to avoid this issue as
|
||||
// discussed here https://github.com/astral-sh/ruff/issues/14315 and
|
||||
// here https://github.com/astral-sh/ruff/blob/71f8389f61a243a0c7584adffc49134ccf792aba/crates/ruff_python_parser/src/parser/statement.rs#L3176-L3179
|
||||
let parameters_by_start = parameters
|
||||
.iter()
|
||||
.sorted_by_key(ruff_text_size::Ranged::start);
|
||||
|
||||
for any_param in parameters_by_start {
|
||||
let parameter = any_param.as_parameter();
|
||||
|
||||
let token_type = match any_param {
|
||||
@@ -515,7 +522,11 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
|
||||
// Handle parameter type annotations
|
||||
if let Some(annotation) = ¶meter.annotation {
|
||||
self.visit_type_annotation(annotation);
|
||||
self.visit_annotation(annotation);
|
||||
}
|
||||
|
||||
if let Some(default) = any_param.default() {
|
||||
self.visit_expr(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,7 +578,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
|
||||
// Handle return type annotation
|
||||
if let Some(returns) = &func.returns {
|
||||
self.visit_type_annotation(returns);
|
||||
self.visit_annotation(returns);
|
||||
}
|
||||
|
||||
// Clear the in_class_scope flag so inner functions
|
||||
@@ -614,21 +625,6 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
self.visit_body(&class.body);
|
||||
self.in_class_scope = prev_in_class;
|
||||
}
|
||||
ast::Stmt::AnnAssign(assign) => {
|
||||
// Handle annotated assignments (e.g., x: int = 5)
|
||||
if let ast::Expr::Name(name) = assign.target.as_ref() {
|
||||
let (token_type, modifiers) = self.classify_name(name);
|
||||
self.add_token(name, token_type, modifiers);
|
||||
}
|
||||
|
||||
// Handle the type annotation
|
||||
self.visit_type_annotation(&assign.annotation);
|
||||
|
||||
// Handle the value if present
|
||||
if let Some(value) = &assign.value {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
}
|
||||
ast::Stmt::Import(import) => {
|
||||
for alias in &import.names {
|
||||
if let Some(asname) = &alias.asname {
|
||||
@@ -690,6 +686,13 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'_ Expr) {
|
||||
let prev_in_type_annotation = self.in_type_annotation;
|
||||
self.in_type_annotation = true;
|
||||
self.visit_expr(expr);
|
||||
self.in_type_annotation = prev_in_type_annotation;
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
match expr {
|
||||
ast::Expr::Name(name) => {
|
||||
@@ -829,23 +832,25 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
);
|
||||
|
||||
// Visit bound expression (for TypeVar)
|
||||
// TODO: We don't call `walk_type_param` here, because, as of today (20th Oct 2025),
|
||||
// `walk_type_param` calls `visit_expr` instead of `visit_annotation`.
|
||||
match type_param {
|
||||
TypeParam::TypeVar(type_var) => {
|
||||
if let Some(bound) = &type_var.bound {
|
||||
self.visit_type_annotation(bound);
|
||||
self.visit_annotation(bound);
|
||||
}
|
||||
if let Some(default) = &type_var.default {
|
||||
self.visit_type_annotation(default);
|
||||
self.visit_annotation(default);
|
||||
}
|
||||
}
|
||||
TypeParam::ParamSpec(param_spec) => {
|
||||
if let Some(default) = ¶m_spec.default {
|
||||
self.visit_type_annotation(default);
|
||||
self.visit_annotation(default);
|
||||
}
|
||||
}
|
||||
TypeParam::TypeVarTuple(type_var_tuple) => {
|
||||
if let Some(default) = &type_var_tuple.default {
|
||||
self.visit_type_annotation(default);
|
||||
self.visit_annotation(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,8 +1798,12 @@ class BoundedContainer[T: int, U = str]:
|
||||
"T" @ 554..555: TypeParameter
|
||||
"value2" @ 557..563: Parameter
|
||||
"U" @ 565..566: TypeParameter
|
||||
"self" @ 577..581: Variable
|
||||
"value1" @ 582..588: Variable
|
||||
"T" @ 590..591: TypeParameter
|
||||
"value1" @ 594..600: Parameter
|
||||
"self" @ 609..613: Variable
|
||||
"value2" @ 614..620: Variable
|
||||
"U" @ 622..623: TypeParameter
|
||||
"value2" @ 626..632: Parameter
|
||||
"get_first" @ 642..651: Method [definition]
|
||||
@@ -2222,4 +2231,67 @@ finally:
|
||||
"e" @ 146..147: Variable
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self_attribute_expression() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Self
|
||||
|
||||
|
||||
class C:
|
||||
def __init__(self: Self):
|
||||
self.annotated: int = 1
|
||||
self.non_annotated = 1
|
||||
self.x.test()
|
||||
self.x()<CURSOR>
|
||||
|
||||
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = semantic_tokens_full_file(&test.db, test.cursor.file);
|
||||
|
||||
assert_snapshot!(semantic_tokens_to_snapshot(&test.db, test.cursor.file, &tokens), @r#"
|
||||
"typing" @ 6..12: Namespace
|
||||
"Self" @ 20..24: Variable
|
||||
"C" @ 33..34: Class [definition]
|
||||
"__init__" @ 44..52: Method [definition]
|
||||
"self" @ 53..57: SelfParameter
|
||||
"Self" @ 59..63: TypeParameter
|
||||
"self" @ 74..78: Parameter
|
||||
"annotated" @ 79..88: Variable
|
||||
"int" @ 90..93: Class
|
||||
"1" @ 96..97: Number
|
||||
"self" @ 106..110: Parameter
|
||||
"non_annotated" @ 111..124: Variable
|
||||
"1" @ 127..128: Number
|
||||
"self" @ 137..141: Parameter
|
||||
"x" @ 142..143: Variable
|
||||
"test" @ 144..148: Variable
|
||||
"self" @ 159..163: Parameter
|
||||
"x" @ 164..165: Variable
|
||||
"#);
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ty/issues/1406>
|
||||
#[test]
|
||||
fn test_invalid_kwargs() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def foo(self, **<CURSOR>key, value=10):
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = semantic_tokens_full_file(&test.db, test.cursor.file);
|
||||
|
||||
assert_snapshot!(semantic_tokens_to_snapshot(&test.db, test.cursor.file, &tokens), @r#"
|
||||
"foo" @ 5..8: Function [definition]
|
||||
"self" @ 9..13: Parameter
|
||||
"key" @ 17..20: Parameter
|
||||
"value" @ 22..27: Parameter
|
||||
"10" @ 28..30: Number
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -54,6 +55,7 @@ default = ["zstd"]
|
||||
deflate = ["ty_vendored/deflate"]
|
||||
schemars = [
|
||||
"dep:schemars",
|
||||
"dep:serde_json",
|
||||
"ruff_db/schemars",
|
||||
"ruff_python_ast/schemars",
|
||||
"ty_python_semantic/schemars",
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use ty_combine::Combine;
|
||||
use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
|
||||
use ty_python_semantic::lint::{Level, LintSource, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionFileSource,
|
||||
PythonVersionSource, PythonVersionWithSource, SearchPathSettings, SearchPathValidationError,
|
||||
@@ -784,9 +784,7 @@ impl SrcOptions {
|
||||
Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize, Hash, get_size2::GetSize,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case", transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Rules {
|
||||
#[cfg_attr(feature = "schemars", schemars(with = "schema::Rules"))]
|
||||
#[get_size(ignore)] // TODO: Add `GetSize` support for `OrderMap`.
|
||||
inner: OrderMap<RangedValue<String>, RangedValue<Level>, BuildHasherDefault<FxHasher>>,
|
||||
}
|
||||
@@ -840,28 +838,11 @@ impl Rules {
|
||||
.and_then(|path| system_path_to_file(db, path).ok());
|
||||
|
||||
// TODO: Add a note if the value was configured on the CLI
|
||||
let diagnostic = match error {
|
||||
GetLintError::Unknown(_) => OptionDiagnostic::new(
|
||||
DiagnosticId::UnknownRule,
|
||||
format!("Unknown lint rule `{rule_name}`"),
|
||||
Severity::Warning,
|
||||
),
|
||||
GetLintError::PrefixedWithCategory { suggestion, .. } => {
|
||||
OptionDiagnostic::new(
|
||||
DiagnosticId::UnknownRule,
|
||||
format!(
|
||||
"Unknown lint rule `{rule_name}`. Did you mean `{suggestion}`?"
|
||||
),
|
||||
Severity::Warning,
|
||||
)
|
||||
}
|
||||
|
||||
GetLintError::Removed(_) => OptionDiagnostic::new(
|
||||
DiagnosticId::UnknownRule,
|
||||
format!("Unknown lint rule `{rule_name}`"),
|
||||
Severity::Warning,
|
||||
),
|
||||
};
|
||||
let diagnostic = OptionDiagnostic::new(
|
||||
DiagnosticId::UnknownRule,
|
||||
error.to_string(),
|
||||
Severity::Warning,
|
||||
);
|
||||
|
||||
let annotation = file.map(Span::from).map(|span| {
|
||||
Annotation::primary(span.with_optional_range(rule_name.range()))
|
||||
@@ -1552,62 +1533,55 @@ impl std::error::Error for ToSettingsError {}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::{
|
||||
InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SubschemaValidation,
|
||||
};
|
||||
use ty_python_semantic::lint::Level;
|
||||
|
||||
pub(super) struct Rules;
|
||||
|
||||
impl JsonSchema for Rules {
|
||||
fn schema_name() -> String {
|
||||
"Rules".to_string()
|
||||
impl schemars::JsonSchema for super::Rules {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("Rules")
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
let registry = ty_python_semantic::default_lint_registry();
|
||||
let level_schema = generator.subschema_for::<super::Level>();
|
||||
|
||||
let level_schema = generator.subschema_for::<Level>();
|
||||
|
||||
let properties: schemars::Map<String, Schema> = registry
|
||||
let properties: Map<String, Value> = registry
|
||||
.lints()
|
||||
.iter()
|
||||
.map(|lint| {
|
||||
(
|
||||
lint.name().to_string(),
|
||||
Schema::Object(SchemaObject {
|
||||
metadata: Some(Box::new(Metadata {
|
||||
title: Some(lint.summary().to_string()),
|
||||
description: Some(lint.documentation()),
|
||||
deprecated: lint.status.is_deprecated(),
|
||||
default: Some(lint.default_level.to_string().into()),
|
||||
..Metadata::default()
|
||||
})),
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
one_of: Some(vec![level_schema.clone()]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
let mut schema = schemars::Schema::default();
|
||||
let object = schema.ensure_object();
|
||||
object.insert(
|
||||
"title".to_string(),
|
||||
Value::String(lint.summary().to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"description".to_string(),
|
||||
Value::String(lint.documentation()),
|
||||
);
|
||||
if lint.status.is_deprecated() {
|
||||
object.insert("deprecated".to_string(), Value::Bool(true));
|
||||
}
|
||||
object.insert(
|
||||
"default".to_string(),
|
||||
Value::String(lint.default_level.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"oneOf".to_string(),
|
||||
Value::Array(vec![level_schema.clone().into()]),
|
||||
);
|
||||
|
||||
(lint.name().to_string(), schema.into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
properties,
|
||||
// Allow unknown rules: ty will warn about them.
|
||||
// It gives a better experience when using an older ty version because
|
||||
// the schema will not deny rules that have been removed in newer versions.
|
||||
additional_properties: Some(Box::new(level_schema)),
|
||||
..ObjectValidation::default()
|
||||
})),
|
||||
let mut schema = schemars::json_schema!({ "type": "object" });
|
||||
let object = schema.ensure_object();
|
||||
object.insert("properties".to_string(), Value::Object(properties));
|
||||
// Allow unknown rules: ty will warn about them. It gives a better experience when using an older
|
||||
// ty version because the schema will not deny rules that have been removed in newer versions.
|
||||
object.insert("additionalProperties".to_string(), level_schema.into());
|
||||
|
||||
..Default::default()
|
||||
})
|
||||
schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ impl Drop for ValueSourceGuard {
|
||||
/// or if the values were loaded from different sources.
|
||||
#[derive(Clone, serde::Serialize, get_size2::GetSize)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RangedValue<T> {
|
||||
value: T,
|
||||
#[serde(skip)]
|
||||
@@ -100,6 +99,34 @@ pub struct RangedValue<T> {
|
||||
range: Option<TextRange>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl<T> schemars::JsonSchema for RangedValue<T>
|
||||
where
|
||||
T: schemars::JsonSchema,
|
||||
{
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
T::schema_name()
|
||||
}
|
||||
|
||||
fn schema_id() -> std::borrow::Cow<'static, str> {
|
||||
T::schema_id()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
T::json_schema(generator)
|
||||
}
|
||||
|
||||
fn _schemars_private_non_optional_json_schema(
|
||||
generator: &mut schemars::SchemaGenerator,
|
||||
) -> schemars::Schema {
|
||||
T::_schemars_private_non_optional_json_schema(generator)
|
||||
}
|
||||
|
||||
fn _schemars_private_is_option() -> bool {
|
||||
T::_schemars_private_is_option()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RangedValue<T> {
|
||||
pub fn new(value: T, source: ValueSource) -> Self {
|
||||
Self::with_range(value, source, TextRange::default())
|
||||
|
||||
@@ -43,6 +43,7 @@ rustc-hash = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
@@ -68,6 +69,7 @@ quickcheck = { version = "1.0.3", default-features = false }
|
||||
quickcheck_macros = { version = "1.0.0" }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars", "dep:serde_json"]
|
||||
serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"]
|
||||
testing = []
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ on function-like callables:
|
||||
|
||||
```py
|
||||
def f_wrong(c: Callable[[], None]):
|
||||
# error: [unresolved-attribute] "Type `() -> None` has no attribute `__qualname__`"
|
||||
# error: [unresolved-attribute] "Object of type `() -> None` has no attribute `__qualname__`"
|
||||
c.__qualname__
|
||||
|
||||
# error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`."
|
||||
|
||||
@@ -1260,13 +1260,13 @@ def _(flag1: bool, flag2: bool):
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||
C.x = 100
|
||||
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
|
||||
@@ -1292,7 +1292,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
@@ -1300,7 +1300,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||
|
||||
# Note: we might want to consider ignoring possibly-missing diagnostics for instance attributes eventually,
|
||||
# see the "Possibly unbound/undeclared instance attribute" section below.
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
@@ -1433,7 +1433,7 @@ def _(flag: bool):
|
||||
class C2: ...
|
||||
C = C1 if flag else C2
|
||||
|
||||
# error: [unresolved-attribute] "Type `<class 'C1'> | <class 'C2'>` has no attribute `x`"
|
||||
# error: [unresolved-attribute] "Object of type `<class 'C1'> | <class 'C2'>` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
|
||||
# TODO: This should ideally be a `unresolved-attribute` error. We need better union
|
||||
@@ -1771,7 +1771,7 @@ reveal_type(date.day) # revealed: int
|
||||
reveal_type(date.month) # revealed: int
|
||||
reveal_type(date.year) # revealed: int
|
||||
|
||||
# error: [unresolved-attribute] "Type `Date` has no attribute `century`"
|
||||
# error: [unresolved-attribute] "Object of type `Date` has no attribute `century`"
|
||||
reveal_type(date.century) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -2457,6 +2457,31 @@ class Counter:
|
||||
reveal_type(Counter().count) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
We also handle infinitely nested generics:
|
||||
|
||||
```py
|
||||
class NestedLists:
|
||||
def __init__(self: "NestedLists"):
|
||||
self.x = 1
|
||||
|
||||
def f(self: "NestedLists"):
|
||||
self.x = [self.x]
|
||||
|
||||
reveal_type(NestedLists().x) # revealed: Unknown | Literal[1] | list[Divergent]
|
||||
|
||||
class NestedMixed:
|
||||
def f(self: "NestedMixed"):
|
||||
self.x = [self.x]
|
||||
|
||||
def g(self: "NestedMixed"):
|
||||
self.x = {self.x}
|
||||
|
||||
def h(self: "NestedMixed"):
|
||||
self.x = {"a": self.x}
|
||||
|
||||
reveal_type(NestedMixed().x) # revealed: Unknown | list[Divergent] | set[Divergent] | dict[Unknown | str, Divergent]
|
||||
```
|
||||
|
||||
### Builtin types attributes
|
||||
|
||||
This test can probably be removed eventually, but we currently include it because we do not yet
|
||||
@@ -2558,6 +2583,23 @@ class C:
|
||||
reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
|
||||
```
|
||||
|
||||
That also works if the tuple is not constructed directly:
|
||||
|
||||
```py
|
||||
# from typing import TypeVar, Literal
|
||||
#
|
||||
# T = TypeVar("T")
|
||||
#
|
||||
# def make_tuple(a: T) -> tuple[T, Literal[1]]:
|
||||
# return (a, 1)
|
||||
#
|
||||
# class D:
|
||||
# def f(self, other: "D"):
|
||||
# self.x = make_tuple(other.x)
|
||||
#
|
||||
# reveal_type(D().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
|
||||
```
|
||||
|
||||
## Attributes of standard library modules that aren't yet defined
|
||||
|
||||
For attributes of stdlib modules that exist in future versions, we can give better diagnostics.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user