Compare commits
29 Commits
dcreager/c
...
0.12.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f51a228f04 | ||
|
|
d5e1b7983e | ||
|
|
7dfde3b929 | ||
|
|
b22586fa0e | ||
|
|
c401a6d86e | ||
|
|
7b6abfb030 | ||
|
|
b005cdb7ff | ||
|
|
b96aa4605b | ||
|
|
cc97579c3b | ||
|
|
ef1802b94f | ||
|
|
98df62db79 | ||
|
|
65b39f2ca9 | ||
|
|
585ce12ace | ||
|
|
21ac16db85 | ||
|
|
745742e414 | ||
|
|
ec5660d786 | ||
|
|
b96929ee19 | ||
|
|
fa711fa40f | ||
|
|
1f29a04e9a | ||
|
|
529d81daca | ||
|
|
4887bdf205 | ||
|
|
e917d309f1 | ||
|
|
18ad2848e3 | ||
|
|
5bfffe1aa7 | ||
|
|
b324ae1be3 | ||
|
|
2db4e5dbea | ||
|
|
4090297a11 | ||
|
|
934fd37d2b | ||
|
|
78e5fe0a51 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,5 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.8
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Expand `PTH201` to check all `PurePath` subclasses ([#19440](https://github.com/astral-sh/ruff/pull/19440))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to correctly parse exception tuples ([#19747](https://github.com/astral-sh/ruff/pull/19747))
|
||||
- \[`flake8-errmsg`\] Exclude `typing.cast` from `EM101` ([#19656](https://github.com/astral-sh/ruff/pull/19656))
|
||||
- \[`flake8-simplify`\] Fix raw string handling in `SIM905` for embedded quotes ([#19591](https://github.com/astral-sh/ruff/pull/19591))
|
||||
- \[`flake8-import-conventions`\] Avoid false positives for NFKC-normalized `__debug__` import aliases in `ICN001` ([#19411](https://github.com/astral-sh/ruff/pull/19411))
|
||||
- \[`isort`\] Fix syntax error after docstring ending with backslash (`I002`) ([#19505](https://github.com/astral-sh/ruff/pull/19505))
|
||||
- \[`pylint`\] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present ([#19679](https://github.com/astral-sh/ruff/pull/19679))
|
||||
- \[`pyupgrade`\] Prevent infinite loop with `I002` (`UP010`, `UP035`) ([#19413](https://github.com/astral-sh/ruff/pull/19413))
|
||||
- \[`ruff`\] Parenthesize generator expressions in f-strings (`RUF010`) ([#19434](https://github.com/astral-sh/ruff/pull/19434))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`eradicate`\] Don't flag `pyrefly` pragmas as unused code (`ERA001`) ([#19731](https://github.com/astral-sh/ruff/pull/19731))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Replace "associative" with "commutative" in docs for `RUF036` ([#19706](https://github.com/astral-sh/ruff/pull/19706))
|
||||
- Fix copy and line separator colors in dark mode ([#19630](https://github.com/astral-sh/ruff/pull/19630))
|
||||
- Fix link to `typing` documentation ([#19648](https://github.com/astral-sh/ruff/pull/19648))
|
||||
- \[`refurb`\] Make more examples error out-of-the-box ([#19695](https://github.com/astral-sh/ruff/pull/19695),[#19673](https://github.com/astral-sh/ruff/pull/19673),[#19672](https://github.com/astral-sh/ruff/pull/19672))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Include column numbers in GitLab output format ([#19708](https://github.com/astral-sh/ruff/pull/19708))
|
||||
- Always expand tabs to four spaces in diagnostics ([#19618](https://github.com/astral-sh/ruff/pull/19618))
|
||||
- Update pre-commit's `ruff` id ([#19654](https://github.com/astral-sh/ruff/pull/19654))
|
||||
|
||||
## 0.12.7
|
||||
|
||||
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
|
||||
|
||||
148
Cargo.lock
generated
148
Cargo.lock
generated
@@ -56,9 +56,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.19"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -95,18 +95,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-svg"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c"
|
||||
checksum = "dc03a770ef506fe1396c0e476120ac0e6523cf14b74218dd5f18cd6833326fa9"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
@@ -117,13 +117,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.9"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -346,9 +346,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
version = "1.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.42"
|
||||
version = "4.5.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
|
||||
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.42"
|
||||
version = "4.5.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
|
||||
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -492,9 +492,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29180405ab3b37bb020246ea66bf8ae233708766fd59581ae929feaef10ce91"
|
||||
checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 1.3.3",
|
||||
@@ -510,9 +510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2454d874ca820ffd71273565530ad318f413195bbc99dce6c958ca07db362c63"
|
||||
checksum = "78f6c1c6bed5fd84d319e8b0889da051daa361c79b7709c9394dfe1a882bba67"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
@@ -521,9 +521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093a9383cdd1a5a0bd1a47cdafb49ae0c6dcd0793c8fb8f79768bab423128c9c"
|
||||
checksum = "c989289ce6b1cbde72ed560496cb8fbf5aa14d5ef5666f168e7f87751038352e"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
@@ -546,9 +546,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1c73bce1e3f47738bf74a6b58b72a49b4f40c837ce420d8d65a270298592aac"
|
||||
checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-divan-compat-macros",
|
||||
@@ -557,9 +557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea51dd8add7eba774cc24b4a98324252ac3ec092ccb5f07e52bbe1cb72a6d373"
|
||||
checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools 0.14.0",
|
||||
@@ -571,9 +571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "3.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417e9edfc4b0289d4b9b48e62f98c6168d5e30c0e612b2935e394b0dd930fe83"
|
||||
checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -885,9 +885,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b"
|
||||
checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1161,9 +1161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
|
||||
checksum = "75a17a226478b2e8294ded60782c03efe54476aa8cd1371d0e5ad9d1071e74e0"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1172,9 +1172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
|
||||
checksum = "5697765925a05c9d401dd04a93dfd662d336cc25fdcc3301220385a1ffcfdde5"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
@@ -1805,9 +1805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
@@ -1856,9 +1856,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "lsp-server"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9462c4dc73e17f971ec1f171d44bfffb72e65a130117233388a0ebc7ec5656f9"
|
||||
checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
@@ -2671,9 +2671,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
@@ -2743,7 +2743,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2795,7 +2795,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tikv-jemallocator",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
"wild",
|
||||
@@ -2811,7 +2811,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"serde",
|
||||
"snapbox",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -2874,6 +2874,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_memory_usage",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -2927,7 +2928,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -2995,7 +2996,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3048,7 +3049,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -3068,6 +3069,13 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_memory_usage"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_notebook"
|
||||
version = "0.0.0"
|
||||
@@ -3298,7 +3306,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
@@ -3327,7 +3335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3388,7 +3396,8 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3441,7 +3450,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d66fe331d546216132ace503512b94d5c68d2c50#d66fe331d546216132ace503512b94d5c68d2c50"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3465,12 +3474,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d66fe331d546216132ace503512b94d5c68d2c50#d66fe331d546216132ace503512b94d5c68d2c50"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d66fe331d546216132ace503512b94d5c68d2c50#d66fe331d546216132ace503512b94d5c68d2c50"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4022,9 +4031,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -4068,9 +4077,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -4203,10 +4212,11 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_server",
|
||||
@@ -4214,6 +4224,16 @@ dependencies = [
|
||||
"wild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_combine"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"ordermap",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
@@ -4255,6 +4275,7 @@ dependencies = [
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_options_metadata",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
@@ -4264,8 +4285,9 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4296,6 +4318,7 @@ dependencies = [
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
@@ -4309,6 +4332,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strsim",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
@@ -4336,6 +4360,7 @@ dependencies = [
|
||||
"lsp-types",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
@@ -4349,6 +4374,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_ide",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
@@ -4387,7 +4413,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -5219,9 +5245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.2"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
|
||||
@@ -23,6 +23,7 @@ ruff_graph = { path = "crates/ruff_graph" }
|
||||
ruff_index = { path = "crates/ruff_index" }
|
||||
ruff_linter = { path = "crates/ruff_linter" }
|
||||
ruff_macros = { path = "crates/ruff_macros" }
|
||||
ruff_memory_usage = { path = "crates/ruff_memory_usage" }
|
||||
ruff_notebook = { path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||
@@ -40,6 +41,7 @@ ruff_text_size = { path = "crates/ruff_text_size" }
|
||||
ruff_workspace = { path = "crates/ruff_workspace" }
|
||||
|
||||
ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
@@ -83,7 +85,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.6.0", features = [
|
||||
get-size2 = { version = "0.6.2", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -141,7 +143,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "d66fe331d546216132ace503512b94d5c68d2c50", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "b121ee46c4483ba74c19e933a3522bd548eb7343", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.12.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.7/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.8/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.8/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.7
|
||||
rev: v0.12.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -798,7 +798,7 @@ fn stdin_parse_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:16: SyntaxError: Expected one or more symbol names after import
|
||||
-:1:16: invalid-syntax: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from foo import
|
||||
| ^
|
||||
@@ -818,14 +818,14 @@ fn stdin_multiple_parse_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:16: SyntaxError: Expected one or more symbol names after import
|
||||
-:1:16: invalid-syntax: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from foo import
|
||||
| ^
|
||||
2 | bar =
|
||||
|
|
||||
|
||||
-:2:6: SyntaxError: Expected an expression
|
||||
-:2:6: invalid-syntax: Expected an expression
|
||||
|
|
||||
1 | from foo import
|
||||
2 | bar =
|
||||
@@ -847,7 +847,7 @@ fn parse_error_not_included() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:6: SyntaxError: Expected an expression
|
||||
-:1:6: invalid-syntax: Expected an expression
|
||||
|
|
||||
1 | foo =
|
||||
| ^
|
||||
|
||||
@@ -4996,6 +4996,37 @@ fn flake8_import_convention_invalid_aliases_config_module_name() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_import_convention_nfkc_normalization() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.flake8-import-conventions.aliases]
|
||||
"test.module" = "_﹏𝘥𝘦𝘣𝘶𝘨﹏﹏"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
, @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Invalid alias for module 'test.module': alias normalizes to '__debug__', which is not allowed.
|
||||
");});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_import_convention_unused_aliased_import() {
|
||||
assert_cmd_snapshot!(
|
||||
@@ -5389,7 +5420,7 @@ fn walrus_before_py38() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:1:2: SyntaxError: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
|
||||
test.py:1:2: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -5435,15 +5466,15 @@ match 2:
|
||||
print("it's one")
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
test.py:2:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
"
|
||||
);
|
||||
|
||||
// syntax error on 3.9 with preview
|
||||
@@ -5464,7 +5495,7 @@ match 2:
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
test.py:2:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -5492,7 +5523,7 @@ fn cache_syntax_errors() -> Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
@@ -5505,7 +5536,7 @@ fn cache_syntax_errors() -> Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
@@ -5618,7 +5649,7 @@ fn semantic_syntax_errors() -> Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:3: SyntaxError: assignment expression cannot rebind comprehension variable
|
||||
main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
|
||||
main.py:1:20: F821 Undefined name `foo`
|
||||
|
||||
----- stderr -----
|
||||
@@ -5632,7 +5663,7 @@ fn semantic_syntax_errors() -> Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:3: SyntaxError: assignment expression cannot rebind comprehension variable
|
||||
main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
|
||||
main.py:1:20: F821 Undefined name `foo`
|
||||
|
||||
----- stderr -----
|
||||
@@ -5651,7 +5682,7 @@ fn semantic_syntax_errors() -> Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:3: SyntaxError: assignment expression cannot rebind comprehension variable
|
||||
-:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -18,6 +18,6 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused
|
||||
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=2;columnnumber=5;code=F821;]Undefined name `y`
|
||||
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=3;columnnumber=1;]SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=3;columnnumber=1;code=invalid-syntax;]Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -18,7 +18,7 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
input.py:1:8: F401 [*] `os` imported but unused
|
||||
input.py:2:5: F821 Undefined name `y`
|
||||
input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
Found 3 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ input.py:2:5: F821 Undefined name `y`
|
||||
4 | case _: ...
|
||||
|
|
||||
|
||||
input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
|
||||
1 | import os # F401
|
||||
2 | x = y # F821
|
||||
|
||||
@@ -18,6 +18,6 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
::error title=Ruff (F401),file=[TMP]/input.py,line=1,col=8,endLine=1,endColumn=10::input.py:1:8: F401 `os` imported but unused
|
||||
::error title=Ruff (F821),file=[TMP]/input.py,line=2,col=5,endLine=2,endColumn=6::input.py:2:5: F821 Undefined name `y`
|
||||
::error title=Ruff,file=[TMP]/input.py,line=3,col=1,endLine=3,endColumn=6::input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
::error title=Ruff (invalid-syntax),file=[TMP]/input.py,line=3,col=1,endLine=3,endColumn=6::input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -19,7 +19,7 @@ exit_code: 1
|
||||
input.py:
|
||||
1:8 F401 [*] `os` imported but unused
|
||||
2:5 F821 Undefined name `y`
|
||||
3:1 SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
3:1 invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
Found 3 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -18,6 +18,6 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"[TMP]/input.py","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":null,"code":"F821","end_location":{"column":6,"row":2},"filename":"[TMP]/input.py","fix":null,"location":{"column":5,"row":2},"message":"Undefined name `y`","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
{"cell":null,"code":null,"end_location":{"column":6,"row":3},"filename":"[TMP]/input.py","fix":null,"location":{"column":1,"row":3},"message":"SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":6,"row":3},"filename":"[TMP]/input.py","fix":null,"location":{"column":1,"row":3},"message":"Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)","noqa_row":null,"url":null}
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -69,7 +69,7 @@ exit_code: 1
|
||||
},
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"code": "invalid-syntax",
|
||||
"end_location": {
|
||||
"column": 6,
|
||||
"row": 3
|
||||
@@ -80,7 +80,7 @@ exit_code: 1
|
||||
"column": 1,
|
||||
"row": 3
|
||||
},
|
||||
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
|
||||
"message": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
|
||||
"noqa_row": null,
|
||||
"url": null
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ exit_code: 1
|
||||
<failure message="Undefined name `y`">line 2, col 5, Undefined name `y`</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff.invalid-syntax" classname="[TMP]/input" line="3" column="1">
|
||||
<failure message="SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)">line 3, col 1, SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)</failure>
|
||||
<failure message="Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)">line 3, col 1, Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
@@ -18,6 +18,6 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
input.py:1: [F401] `os` imported but unused
|
||||
input.py:2: [F821] Undefined name `y`
|
||||
input.py:3: [invalid-syntax] SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
input.py:3: [invalid-syntax] Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -90,7 +90,7 @@ exit_code: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
"message": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
}
|
||||
],
|
||||
"severity": "WARNING",
|
||||
|
||||
@@ -83,9 +83,9 @@ exit_code: 1
|
||||
}
|
||||
],
|
||||
"message": {
|
||||
"text": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
"text": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
},
|
||||
"ruleId": null
|
||||
"ruleId": "invalid-syntax"
|
||||
}
|
||||
],
|
||||
"tool": {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![expect(clippy::needless_doctest_main)]
|
||||
|
||||
//! A library for formatting of text or programming code snippets.
|
||||
//!
|
||||
//! It's primary purpose is to build an ASCII-graphical representation of the snippet
|
||||
|
||||
@@ -193,9 +193,14 @@ impl DisplaySet<'_> {
|
||||
stylesheet: &Stylesheet,
|
||||
buffer: &mut StyledBuffer,
|
||||
) -> fmt::Result {
|
||||
let hide_severity = annotation.annotation_type.is_none();
|
||||
let color = get_annotation_style(&annotation.annotation_type, stylesheet);
|
||||
let formatted_len = if let Some(id) = &annotation.id {
|
||||
2 + id.len() + annotation_type_len(&annotation.annotation_type)
|
||||
if hide_severity {
|
||||
id.len()
|
||||
} else {
|
||||
2 + id.len() + annotation_type_len(&annotation.annotation_type)
|
||||
}
|
||||
} else {
|
||||
annotation_type_len(&annotation.annotation_type)
|
||||
};
|
||||
@@ -209,18 +214,66 @@ impl DisplaySet<'_> {
|
||||
if formatted_len == 0 {
|
||||
self.format_label(line_offset, &annotation.label, stylesheet, buffer)
|
||||
} else {
|
||||
let id = match &annotation.id {
|
||||
Some(id) => format!("[{id}]"),
|
||||
None => String::new(),
|
||||
};
|
||||
buffer.append(
|
||||
line_offset,
|
||||
&format!("{}{}", annotation_type_str(&annotation.annotation_type), id),
|
||||
*color,
|
||||
);
|
||||
// TODO(brent) All of this complicated checking of `hide_severity` should be reverted
|
||||
// once we have real severities in Ruff. This code is trying to account for two
|
||||
// different cases:
|
||||
//
|
||||
// - main diagnostic message
|
||||
// - subdiagnostic message
|
||||
//
|
||||
// In the first case, signaled by `hide_severity = true`, we want to print the ID (the
|
||||
// noqa code for a ruff lint diagnostic, e.g. `F401`, or `invalid-syntax` for a syntax
|
||||
// error) without brackets. Instead, for subdiagnostics, we actually want to print the
|
||||
// severity (usually `help`) regardless of the `hide_severity` setting. This is signaled
|
||||
// by an ID of `None`.
|
||||
//
|
||||
// With real severities these should be reported more like in ty:
|
||||
//
|
||||
// ```
|
||||
// error[F401]: `math` imported but unused
|
||||
// error[invalid-syntax]: Cannot use `match` statement on Python 3.9...
|
||||
// ```
|
||||
//
|
||||
// instead of the current versions intended to mimic the old Ruff output format:
|
||||
//
|
||||
// ```
|
||||
// F401 `math` imported but unused
|
||||
// invalid-syntax: Cannot use `match` statement on Python 3.9...
|
||||
// ```
|
||||
//
|
||||
// Note that the `invalid-syntax` colon is added manually in `ruff_db`, not here. We
|
||||
// could eventually add a colon to Ruff lint diagnostics (`F401:`) and then make the
|
||||
// colon below unconditional again.
|
||||
//
|
||||
// This also applies to the hard-coded `stylesheet.error()` styling of the
|
||||
// hidden-severity `id`. This should just be `*color` again later, but for now we don't
|
||||
// want an unformatted `id`, which is what `get_annotation_style` returns for
|
||||
// `DisplayAnnotationType::None`.
|
||||
let annotation_type = annotation_type_str(&annotation.annotation_type);
|
||||
if let Some(id) = annotation.id {
|
||||
if hide_severity {
|
||||
buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
|
||||
} else {
|
||||
buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
|
||||
}
|
||||
} else {
|
||||
buffer.append(line_offset, annotation_type, *color);
|
||||
}
|
||||
|
||||
if annotation.is_fixable {
|
||||
buffer.append(line_offset, "[", stylesheet.none);
|
||||
buffer.append(line_offset, "*", stylesheet.help);
|
||||
buffer.append(line_offset, "]", stylesheet.none);
|
||||
// In the hide-severity case, we need a space instead of the colon and space below.
|
||||
if hide_severity {
|
||||
buffer.append(line_offset, " ", stylesheet.none);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_annotation_empty(annotation) {
|
||||
buffer.append(line_offset, ": ", stylesheet.none);
|
||||
if annotation.id.is_none() || !hide_severity {
|
||||
buffer.append(line_offset, ": ", stylesheet.none);
|
||||
}
|
||||
self.format_label(line_offset, &annotation.label, stylesheet, buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -249,11 +302,15 @@ impl DisplaySet<'_> {
|
||||
let lineno_color = stylesheet.line_no();
|
||||
buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
|
||||
buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none);
|
||||
if let Some((col, row)) = pos {
|
||||
buffer.append(line_offset, ":", stylesheet.none);
|
||||
buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
|
||||
if let Some(Position { row, col, cell }) = pos {
|
||||
if let Some(cell) = cell {
|
||||
buffer.append(line_offset, ":", stylesheet.none);
|
||||
buffer.append(line_offset, &format!("cell {cell}"), stylesheet.none);
|
||||
}
|
||||
buffer.append(line_offset, ":", stylesheet.none);
|
||||
buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
|
||||
buffer.append(line_offset, ":", stylesheet.none);
|
||||
buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -768,6 +825,7 @@ pub(crate) struct Annotation<'a> {
|
||||
pub(crate) annotation_type: DisplayAnnotationType,
|
||||
pub(crate) id: Option<&'a str>,
|
||||
pub(crate) label: Vec<DisplayTextFragment<'a>>,
|
||||
pub(crate) is_fixable: bool,
|
||||
}
|
||||
|
||||
/// A single line used in `DisplayList`.
|
||||
@@ -833,6 +891,13 @@ impl DisplaySourceAnnotation<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Position {
|
||||
row: usize,
|
||||
col: usize,
|
||||
cell: Option<usize>,
|
||||
}
|
||||
|
||||
/// Raw line - a line which does not have the `lineno` part and is not considered
|
||||
/// a part of the snippet.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -841,7 +906,7 @@ pub(crate) enum DisplayRawLine<'a> {
|
||||
/// slice in the project structure.
|
||||
Origin {
|
||||
path: &'a str,
|
||||
pos: Option<(usize, usize)>,
|
||||
pos: Option<Position>,
|
||||
header_type: DisplayHeaderType,
|
||||
},
|
||||
|
||||
@@ -920,6 +985,13 @@ pub(crate) enum DisplayAnnotationType {
|
||||
Help,
|
||||
}
|
||||
|
||||
impl DisplayAnnotationType {
|
||||
#[inline]
|
||||
const fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<snippet::Level> for DisplayAnnotationType {
|
||||
fn from(at: snippet::Level) -> Self {
|
||||
match at {
|
||||
@@ -1015,11 +1087,12 @@ fn format_message<'m>(
|
||||
title,
|
||||
footer,
|
||||
snippets,
|
||||
is_fixable,
|
||||
} = message;
|
||||
|
||||
let mut sets = vec![];
|
||||
let body = if !snippets.is_empty() || primary {
|
||||
vec![format_title(level, id, title)]
|
||||
vec![format_title(level, id, title, is_fixable)]
|
||||
} else {
|
||||
format_footer(level, id, title)
|
||||
};
|
||||
@@ -1060,12 +1133,18 @@ fn format_message<'m>(
|
||||
sets
|
||||
}
|
||||
|
||||
fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
|
||||
fn format_title<'a>(
|
||||
level: crate::Level,
|
||||
id: Option<&'a str>,
|
||||
label: &'a str,
|
||||
is_fixable: bool,
|
||||
) -> DisplayLine<'a> {
|
||||
DisplayLine::Raw(DisplayRawLine::Annotation {
|
||||
annotation: Annotation {
|
||||
annotation_type: DisplayAnnotationType::from(level),
|
||||
id,
|
||||
label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
|
||||
is_fixable,
|
||||
},
|
||||
source_aligned: false,
|
||||
continuation: false,
|
||||
@@ -1084,6 +1163,7 @@ fn format_footer<'a>(
|
||||
annotation_type: DisplayAnnotationType::from(level),
|
||||
id,
|
||||
label: format_label(Some(line), None),
|
||||
is_fixable: false,
|
||||
},
|
||||
source_aligned: true,
|
||||
continuation: i != 0,
|
||||
@@ -1118,6 +1198,23 @@ fn format_snippet<'m>(
|
||||
let main_range = snippet.annotations.first().map(|x| x.range.start);
|
||||
let origin = snippet.origin;
|
||||
let need_empty_header = origin.is_some() || is_first;
|
||||
|
||||
let is_file_level = snippet.annotations.iter().any(|ann| ann.is_file_level);
|
||||
if is_file_level {
|
||||
assert!(
|
||||
snippet.source.is_empty(),
|
||||
"Non-empty file-level snippet that won't be rendered: {:?}",
|
||||
snippet.source
|
||||
);
|
||||
let header = format_header(origin, main_range, &[], is_first, snippet.cell_index);
|
||||
return DisplaySet {
|
||||
display_lines: header.map_or_else(Vec::new, |header| vec![header]),
|
||||
margin: Margin::new(0, 0, 0, 0, term_width, 0),
|
||||
};
|
||||
}
|
||||
|
||||
let cell_index = snippet.cell_index;
|
||||
|
||||
let mut body = format_body(
|
||||
snippet,
|
||||
need_empty_header,
|
||||
@@ -1126,7 +1223,13 @@ fn format_snippet<'m>(
|
||||
anonymized_line_numbers,
|
||||
cut_indicator,
|
||||
);
|
||||
let header = format_header(origin, main_range, &body.display_lines, is_first);
|
||||
let header = format_header(
|
||||
origin,
|
||||
main_range,
|
||||
&body.display_lines,
|
||||
is_first,
|
||||
cell_index,
|
||||
);
|
||||
|
||||
if let Some(header) = header {
|
||||
body.display_lines.insert(0, header);
|
||||
@@ -1146,6 +1249,7 @@ fn format_header<'a>(
|
||||
main_range: Option<usize>,
|
||||
body: &[DisplayLine<'_>],
|
||||
is_first: bool,
|
||||
cell_index: Option<usize>,
|
||||
) -> Option<DisplayLine<'a>> {
|
||||
let display_header = if is_first {
|
||||
DisplayHeaderType::Initial
|
||||
@@ -1182,7 +1286,11 @@ fn format_header<'a>(
|
||||
|
||||
return Some(DisplayLine::Raw(DisplayRawLine::Origin {
|
||||
path,
|
||||
pos: Some((line_offset, col)),
|
||||
pos: Some(Position {
|
||||
row: line_offset,
|
||||
col,
|
||||
cell: cell_index,
|
||||
}),
|
||||
header_type: display_header,
|
||||
}));
|
||||
}
|
||||
@@ -1472,6 +1580,7 @@ fn format_body<'m>(
|
||||
annotation_type,
|
||||
id: None,
|
||||
label: format_label(annotation.label, None),
|
||||
is_fixable: false,
|
||||
},
|
||||
range,
|
||||
annotation_type: DisplayAnnotationType::from(annotation.level),
|
||||
@@ -1511,6 +1620,7 @@ fn format_body<'m>(
|
||||
annotation_type,
|
||||
id: None,
|
||||
label: vec![],
|
||||
is_fixable: false,
|
||||
},
|
||||
range,
|
||||
annotation_type: DisplayAnnotationType::from(annotation.level),
|
||||
@@ -1580,6 +1690,7 @@ fn format_body<'m>(
|
||||
annotation_type,
|
||||
id: None,
|
||||
label: format_label(annotation.label, None),
|
||||
is_fixable: false,
|
||||
},
|
||||
range,
|
||||
annotation_type: DisplayAnnotationType::from(annotation.level),
|
||||
|
||||
@@ -22,6 +22,7 @@ pub struct Message<'a> {
|
||||
pub(crate) title: &'a str,
|
||||
pub(crate) snippets: Vec<Snippet<'a>>,
|
||||
pub(crate) footer: Vec<Message<'a>>,
|
||||
pub(crate) is_fixable: bool,
|
||||
}
|
||||
|
||||
impl<'a> Message<'a> {
|
||||
@@ -49,6 +50,15 @@ impl<'a> Message<'a> {
|
||||
self.footer.extend(footer);
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether or not the diagnostic for this message is fixable.
|
||||
///
|
||||
/// This is rendered as a `[*]` indicator after the `id` in an annotation header, if the
|
||||
/// annotation also has `Level::None`.
|
||||
pub fn is_fixable(mut self, yes: bool) -> Self {
|
||||
self.is_fixable = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure containing the slice of text to be annotated and
|
||||
@@ -65,6 +75,10 @@ pub struct Snippet<'a> {
|
||||
pub(crate) annotations: Vec<Annotation<'a>>,
|
||||
|
||||
pub(crate) fold: bool,
|
||||
|
||||
/// The optional cell index in a Jupyter notebook, used for reporting source locations along
|
||||
/// with the ranges on `annotations`.
|
||||
pub(crate) cell_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> Snippet<'a> {
|
||||
@@ -75,6 +89,7 @@ impl<'a> Snippet<'a> {
|
||||
source,
|
||||
annotations: vec![],
|
||||
fold: false,
|
||||
cell_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +118,12 @@ impl<'a> Snippet<'a> {
|
||||
self.fold = fold;
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach a Jupyter notebook cell index.
|
||||
pub fn cell_index(mut self, index: Option<usize>) -> Self {
|
||||
self.cell_index = index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An annotation for a [`Snippet`].
|
||||
@@ -114,6 +135,7 @@ pub struct Annotation<'a> {
|
||||
pub(crate) range: Range<usize>,
|
||||
pub(crate) label: Option<&'a str>,
|
||||
pub(crate) level: Level,
|
||||
pub(crate) is_file_level: bool,
|
||||
}
|
||||
|
||||
impl<'a> Annotation<'a> {
|
||||
@@ -121,6 +143,11 @@ impl<'a> Annotation<'a> {
|
||||
self.label = Some(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_file_level(mut self, yes: bool) -> Self {
|
||||
self.is_file_level = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of annotations.
|
||||
@@ -145,6 +172,7 @@ impl Level {
|
||||
title,
|
||||
snippets: vec![],
|
||||
footer: vec![],
|
||||
is_fixable: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +182,7 @@ impl Level {
|
||||
range: span,
|
||||
label: None,
|
||||
level: self,
|
||||
is_file_level: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ license = { workspace = true }
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true, optional = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_memory_usage = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["get-size"] }
|
||||
ruff_python_parser = { workspace = true }
|
||||
|
||||
@@ -212,7 +212,7 @@ impl Diagnostic {
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
pub fn concise_message(&self) -> ConciseMessage {
|
||||
pub fn concise_message(&self) -> ConciseMessage<'_> {
|
||||
let main = self.inner.message.as_str();
|
||||
let annotation = self
|
||||
.primary_annotation()
|
||||
@@ -366,6 +366,16 @@ impl Diagnostic {
|
||||
self.inner.secondary_code.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the secondary code for the diagnostic if it exists, or the lint name otherwise.
|
||||
///
|
||||
/// This is a common pattern for Ruff diagnostics, which want to use the noqa code in general,
|
||||
/// but fall back on the `invalid-syntax` identifier for syntax errors, which don't have
|
||||
/// secondary codes.
|
||||
pub fn secondary_code_or_id(&self) -> &str {
|
||||
self.secondary_code()
|
||||
.map_or_else(|| self.inner.id.as_str(), SecondaryCode::as_str)
|
||||
}
|
||||
|
||||
/// Set the secondary code for this diagnostic.
|
||||
pub fn set_secondary_code(&mut self, code: SecondaryCode) {
|
||||
Arc::make_mut(&mut self.inner).secondary_code = Some(code);
|
||||
@@ -644,7 +654,7 @@ impl SubDiagnostic {
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
pub fn concise_message(&self) -> ConciseMessage {
|
||||
pub fn concise_message(&self) -> ConciseMessage<'_> {
|
||||
let main = self.inner.message.as_str();
|
||||
let annotation = self
|
||||
.primary_annotation()
|
||||
@@ -702,6 +712,11 @@ pub struct Annotation {
|
||||
is_primary: bool,
|
||||
/// The diagnostic tags associated with this annotation.
|
||||
tags: Vec<DiagnosticTag>,
|
||||
/// Whether this annotation is a file-level or full-file annotation.
|
||||
///
|
||||
/// When set, rendering will only include the file's name and (optional) range. Everything else
|
||||
/// is omitted, including any file snippet or message.
|
||||
is_file_level: bool,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
@@ -720,6 +735,7 @@ impl Annotation {
|
||||
message: None,
|
||||
is_primary: true,
|
||||
tags: Vec::new(),
|
||||
is_file_level: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,6 +752,7 @@ impl Annotation {
|
||||
message: None,
|
||||
is_primary: false,
|
||||
tags: Vec::new(),
|
||||
is_file_level: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,6 +818,21 @@ impl Annotation {
|
||||
pub fn push_tag(&mut self, tag: DiagnosticTag) {
|
||||
self.tags.push(tag);
|
||||
}
|
||||
|
||||
/// Set whether or not this annotation is file-level.
|
||||
///
|
||||
/// File-level annotations are only rendered with their file name and range, if available. This
|
||||
/// is intended for backwards compatibility with Ruff diagnostics, which historically used
|
||||
/// `TextRange::default` to indicate a file-level diagnostic. In the new diagnostic model, a
|
||||
/// [`Span`] with a range of `None` should be used instead, as mentioned in the `Span`
|
||||
/// documentation.
|
||||
///
|
||||
/// TODO(brent) update this usage in Ruff and remove `is_file_level` entirely. See
|
||||
/// <https://github.com/astral-sh/ruff/issues/19688>, especially my first comment, for more
|
||||
/// details.
|
||||
pub fn set_file_level(&mut self, yes: bool) {
|
||||
self.is_file_level = yes;
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
@@ -1067,7 +1099,7 @@ enum DiagnosticSource {
|
||||
|
||||
impl DiagnosticSource {
|
||||
/// Returns this input as a `SourceCode` for convenient querying.
|
||||
fn as_source_code(&self) -> SourceCode {
|
||||
fn as_source_code(&self) -> SourceCode<'_, '_> {
|
||||
match self {
|
||||
DiagnosticSource::Ty(input) => SourceCode::new(input.text.as_str(), &input.line_index),
|
||||
DiagnosticSource::Ruff(source) => SourceCode::new(source.source_text(), source.index()),
|
||||
|
||||
@@ -135,7 +135,7 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
|
||||
.none(stylesheet.none);
|
||||
|
||||
for diag in self.diagnostics {
|
||||
let resolved = Resolved::new(self.resolver, diag);
|
||||
let resolved = Resolved::new(self.resolver, diag, self.config);
|
||||
let renderable = resolved.to_renderable(self.config.context);
|
||||
for diag in renderable.diagnostics.iter() {
|
||||
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
|
||||
@@ -191,9 +191,13 @@ struct Resolved<'a> {
|
||||
|
||||
impl<'a> Resolved<'a> {
|
||||
/// Creates a new resolved set of diagnostics.
|
||||
fn new(resolver: &'a dyn FileResolver, diag: &'a Diagnostic) -> Resolved<'a> {
|
||||
fn new(
|
||||
resolver: &'a dyn FileResolver,
|
||||
diag: &'a Diagnostic,
|
||||
config: &DisplayDiagnosticConfig,
|
||||
) -> Resolved<'a> {
|
||||
let mut diagnostics = vec![];
|
||||
diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, diag));
|
||||
diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, config, diag));
|
||||
for sub in &diag.inner.subs {
|
||||
diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub));
|
||||
}
|
||||
@@ -223,12 +227,14 @@ struct ResolvedDiagnostic<'a> {
|
||||
id: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
is_fixable: bool,
|
||||
}
|
||||
|
||||
impl<'a> ResolvedDiagnostic<'a> {
|
||||
/// Resolve a single diagnostic.
|
||||
fn from_diagnostic(
|
||||
resolver: &'a dyn FileResolver,
|
||||
config: &DisplayDiagnosticConfig,
|
||||
diag: &'a Diagnostic,
|
||||
) -> ResolvedDiagnostic<'a> {
|
||||
let annotations: Vec<_> = diag
|
||||
@@ -238,16 +244,38 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.filter_map(|ann| {
|
||||
let path = ann.span.file.path(resolver);
|
||||
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann)
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
|
||||
})
|
||||
.collect();
|
||||
let id = Some(diag.inner.id.to_string());
|
||||
let message = diag.inner.message.as_str().to_string();
|
||||
|
||||
let id = if config.hide_severity {
|
||||
// Either the rule code alone (e.g. `F401`), or the lint id with a colon (e.g.
|
||||
// `invalid-syntax:`). When Ruff gets real severities, we should put the colon back in
|
||||
// `DisplaySet::format_annotation` for both cases, but this is a small hack to improve
|
||||
// the formatting of syntax errors for now. This should also be kept consistent with the
|
||||
// concise formatting.
|
||||
Some(diag.secondary_code().map_or_else(
|
||||
|| format!("{id}:", id = diag.inner.id),
|
||||
|code| code.to_string(),
|
||||
))
|
||||
} else {
|
||||
Some(diag.inner.id.to_string())
|
||||
};
|
||||
|
||||
let level = if config.hide_severity {
|
||||
AnnotateLevel::None
|
||||
} else {
|
||||
diag.inner.severity.to_annotate()
|
||||
};
|
||||
|
||||
ResolvedDiagnostic {
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
level,
|
||||
id,
|
||||
message,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
is_fixable: diag
|
||||
.fix()
|
||||
.is_some_and(|fix| fix.applies(config.fix_applicability)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +291,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.filter_map(|ann| {
|
||||
let path = ann.span.file.path(resolver);
|
||||
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann)
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
|
||||
})
|
||||
.collect();
|
||||
ResolvedDiagnostic {
|
||||
@@ -271,6 +299,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
id: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
is_fixable: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,20 +330,49 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
&prev.diagnostic_source.as_source_code(),
|
||||
context,
|
||||
prev.line_end,
|
||||
prev.notebook_index.as_ref(),
|
||||
)
|
||||
.get();
|
||||
let this_context_begins = context_before(
|
||||
&ann.diagnostic_source.as_source_code(),
|
||||
context,
|
||||
ann.line_start,
|
||||
ann.notebook_index.as_ref(),
|
||||
)
|
||||
.get();
|
||||
|
||||
// For notebooks, check whether the end of the
|
||||
// previous annotation and the start of the current
|
||||
// annotation are in different cells.
|
||||
let prev_cell_index = prev.notebook_index.as_ref().map(|notebook_index| {
|
||||
let prev_end = prev
|
||||
.diagnostic_source
|
||||
.as_source_code()
|
||||
.line_column(prev.range.end());
|
||||
notebook_index.cell(prev_end.line).unwrap_or_default().get()
|
||||
});
|
||||
let this_cell_index = ann.notebook_index.as_ref().map(|notebook_index| {
|
||||
let this_start = ann
|
||||
.diagnostic_source
|
||||
.as_source_code()
|
||||
.line_column(ann.range.start());
|
||||
notebook_index
|
||||
.cell(this_start.line)
|
||||
.unwrap_or_default()
|
||||
.get()
|
||||
});
|
||||
let in_different_cells = prev_cell_index != this_cell_index;
|
||||
|
||||
// The boundary case here is when `prev_context_ends`
|
||||
// is exactly one less than `this_context_begins`. In
|
||||
// that case, the context windows are adjacent and we
|
||||
// should fall through below to add this annotation to
|
||||
// the existing snippet.
|
||||
if this_context_begins.saturating_sub(prev_context_ends) > 1 {
|
||||
//
|
||||
// For notebooks, also check that the context windows
|
||||
// are in the same cell. Windows from different cells
|
||||
// should never be considered adjacent.
|
||||
if in_different_cells || this_context_begins.saturating_sub(prev_context_ends) > 1 {
|
||||
snippet_by_path
|
||||
.entry(path)
|
||||
.or_default()
|
||||
@@ -338,6 +396,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
id: self.id.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
is_fixable: self.is_fixable,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +416,8 @@ struct ResolvedAnnotation<'a> {
|
||||
line_end: OneIndexed,
|
||||
message: Option<&'a str>,
|
||||
is_primary: bool,
|
||||
is_file_level: bool,
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
}
|
||||
|
||||
impl<'a> ResolvedAnnotation<'a> {
|
||||
@@ -369,6 +430,7 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
path: &'a str,
|
||||
diagnostic_source: &DiagnosticSource,
|
||||
ann: &'a Annotation,
|
||||
resolver: &'a dyn FileResolver,
|
||||
) -> Option<ResolvedAnnotation<'a>> {
|
||||
let source = diagnostic_source.as_source_code();
|
||||
let (range, line_start, line_end) = match (ann.span.range(), ann.message.is_some()) {
|
||||
@@ -402,6 +464,8 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
line_end,
|
||||
message: ann.get_message(),
|
||||
is_primary: ann.is_primary,
|
||||
is_file_level: ann.is_file_level,
|
||||
notebook_index: resolver.notebook_index(&ann.span.file),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -436,6 +500,10 @@ struct RenderableDiagnostic<'r> {
|
||||
/// should be from the same file, and none of the snippets inside of a
|
||||
/// collection should overlap with one another or be directly adjacent.
|
||||
snippets_by_input: Vec<RenderableSnippets<'r>>,
|
||||
/// Whether or not the diagnostic is fixable.
|
||||
///
|
||||
/// This is rendered as a `[*]` indicator after the diagnostic ID.
|
||||
is_fixable: bool,
|
||||
}
|
||||
|
||||
impl RenderableDiagnostic<'_> {
|
||||
@@ -448,7 +516,7 @@ impl RenderableDiagnostic<'_> {
|
||||
.iter()
|
||||
.map(|snippet| snippet.to_annotate(path))
|
||||
});
|
||||
let mut message = self.level.title(self.message);
|
||||
let mut message = self.level.title(self.message).is_fixable(self.is_fixable);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id(id);
|
||||
}
|
||||
@@ -530,17 +598,27 @@ struct RenderableSnippet<'r> {
|
||||
/// Whether this snippet contains at least one primary
|
||||
/// annotation.
|
||||
has_primary: bool,
|
||||
/// The cell index in a Jupyter notebook, if this snippet refers to a notebook.
|
||||
///
|
||||
/// This is used for rendering annotations with offsets like `cell 1:2:3` instead of simple row
|
||||
/// and column numbers.
|
||||
cell_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'r> RenderableSnippet<'r> {
|
||||
/// Creates a new snippet with one or more annotations that is ready to be
|
||||
/// renderer.
|
||||
/// rendered.
|
||||
///
|
||||
/// The first line of the snippet is the smallest line number on which one
|
||||
/// of the annotations begins, minus the context window size. The last line
|
||||
/// is the largest line number on which one of the annotations ends, plus
|
||||
/// the context window size.
|
||||
///
|
||||
/// For Jupyter notebooks, the context window may also be truncated at cell
|
||||
/// boundaries. If multiple annotations are present, and they point to
|
||||
/// different cells, these will have already been split into separate
|
||||
/// snippets by `ResolvedDiagnostic::to_renderable`.
|
||||
///
|
||||
/// Callers should guarantee that the `input` on every `ResolvedAnnotation`
|
||||
/// given is identical.
|
||||
///
|
||||
@@ -557,19 +635,19 @@ impl<'r> RenderableSnippet<'r> {
|
||||
"creating a renderable snippet requires a non-zero number of annotations",
|
||||
);
|
||||
let diagnostic_source = &anns[0].diagnostic_source;
|
||||
let notebook_index = anns[0].notebook_index.as_ref();
|
||||
let source = diagnostic_source.as_source_code();
|
||||
let has_primary = anns.iter().any(|ann| ann.is_primary);
|
||||
|
||||
let line_start = context_before(
|
||||
&source,
|
||||
context,
|
||||
anns.iter().map(|ann| ann.line_start).min().unwrap(),
|
||||
);
|
||||
let line_end = context_after(
|
||||
&source,
|
||||
context,
|
||||
anns.iter().map(|ann| ann.line_end).max().unwrap(),
|
||||
);
|
||||
let content_start_index = anns.iter().map(|ann| ann.line_start).min().unwrap();
|
||||
let line_start = context_before(&source, context, content_start_index, notebook_index);
|
||||
|
||||
let start = source.line_column(anns[0].range.start());
|
||||
let cell_index = notebook_index
|
||||
.map(|notebook_index| notebook_index.cell(start.line).unwrap_or_default().get());
|
||||
|
||||
let content_end_index = anns.iter().map(|ann| ann.line_end).max().unwrap();
|
||||
let line_end = context_after(&source, context, content_end_index, notebook_index);
|
||||
|
||||
let snippet_start = source.line_start(line_start);
|
||||
let snippet_end = source.line_end(line_end);
|
||||
@@ -587,11 +665,18 @@ impl<'r> RenderableSnippet<'r> {
|
||||
annotations,
|
||||
} = replace_unprintable(snippet, annotations).fix_up_empty_spans_after_line_terminator();
|
||||
|
||||
let line_start = notebook_index.map_or(line_start, |notebook_index| {
|
||||
notebook_index
|
||||
.cell_row(line_start)
|
||||
.unwrap_or(OneIndexed::MIN)
|
||||
});
|
||||
|
||||
RenderableSnippet {
|
||||
snippet,
|
||||
line_start,
|
||||
annotations,
|
||||
has_primary,
|
||||
cell_index,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +690,7 @@ impl<'r> RenderableSnippet<'r> {
|
||||
.iter()
|
||||
.map(RenderableAnnotation::to_annotate),
|
||||
)
|
||||
.cell_index(self.cell_index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,6 +705,8 @@ struct RenderableAnnotation<'r> {
|
||||
message: Option<&'r str>,
|
||||
/// Whether this annotation is considered "primary" or not.
|
||||
is_primary: bool,
|
||||
/// Whether this annotation applies to an entire file, rather than a snippet within it.
|
||||
is_file_level: bool,
|
||||
}
|
||||
|
||||
impl<'r> RenderableAnnotation<'r> {
|
||||
@@ -636,6 +724,7 @@ impl<'r> RenderableAnnotation<'r> {
|
||||
range,
|
||||
message: ann.message,
|
||||
is_primary: ann.is_primary,
|
||||
is_file_level: ann.is_file_level,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +750,7 @@ impl<'r> RenderableAnnotation<'r> {
|
||||
if let Some(message) = self.message {
|
||||
ann = ann.label(message);
|
||||
}
|
||||
ann
|
||||
ann.is_file_level(self.is_file_level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,7 +877,15 @@ pub struct Input {
|
||||
///
|
||||
/// The line number returned is guaranteed to be less than
|
||||
/// or equal to `start`.
|
||||
fn context_before(source: &SourceCode<'_, '_>, len: usize, start: OneIndexed) -> OneIndexed {
|
||||
///
|
||||
/// In Jupyter notebooks, lines outside the cell containing
|
||||
/// `start` will be omitted.
|
||||
fn context_before(
|
||||
source: &SourceCode<'_, '_>,
|
||||
len: usize,
|
||||
start: OneIndexed,
|
||||
notebook_index: Option<&NotebookIndex>,
|
||||
) -> OneIndexed {
|
||||
let mut line = start.saturating_sub(len);
|
||||
// Trim leading empty lines.
|
||||
while line < start {
|
||||
@@ -797,6 +894,17 @@ fn context_before(source: &SourceCode<'_, '_>, len: usize, start: OneIndexed) ->
|
||||
}
|
||||
line = line.saturating_add(1);
|
||||
}
|
||||
|
||||
if let Some(index) = notebook_index {
|
||||
let content_start_cell = index.cell(start).unwrap_or(OneIndexed::MIN);
|
||||
while line < start {
|
||||
if index.cell(line).unwrap_or(OneIndexed::MIN) == content_start_cell {
|
||||
break;
|
||||
}
|
||||
line = line.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
line
|
||||
}
|
||||
|
||||
@@ -806,7 +914,15 @@ fn context_before(source: &SourceCode<'_, '_>, len: usize, start: OneIndexed) ->
|
||||
/// The line number returned is guaranteed to be greater
|
||||
/// than or equal to `start` and no greater than the
|
||||
/// number of lines in `source`.
|
||||
fn context_after(source: &SourceCode<'_, '_>, len: usize, start: OneIndexed) -> OneIndexed {
|
||||
///
|
||||
/// In Jupyter notebooks, lines outside the cell containing
|
||||
/// `start` will be omitted.
|
||||
fn context_after(
|
||||
source: &SourceCode<'_, '_>,
|
||||
len: usize,
|
||||
start: OneIndexed,
|
||||
notebook_index: Option<&NotebookIndex>,
|
||||
) -> OneIndexed {
|
||||
let max_lines = OneIndexed::from_zero_indexed(source.line_count());
|
||||
let mut line = start.saturating_add(len).min(max_lines);
|
||||
// Trim trailing empty lines.
|
||||
@@ -816,6 +932,17 @@ fn context_after(source: &SourceCode<'_, '_>, len: usize, start: OneIndexed) ->
|
||||
}
|
||||
line = line.saturating_sub(1);
|
||||
}
|
||||
|
||||
if let Some(index) = notebook_index {
|
||||
let content_end_cell = index.cell(start).unwrap_or(OneIndexed::MIN);
|
||||
while line > start {
|
||||
if index.cell(line).unwrap_or(OneIndexed::MIN) == content_end_cell {
|
||||
break;
|
||||
}
|
||||
line = line.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
line
|
||||
}
|
||||
|
||||
@@ -2517,7 +2644,12 @@ watermelon
|
||||
/// of the corresponding line minus one. (The "minus one" is because
|
||||
/// otherwise, the span will end where the next line begins, and this
|
||||
/// confuses `ruff_annotate_snippets` as of 2025-03-13.)
|
||||
fn span(&self, path: &str, line_offset_start: &str, line_offset_end: &str) -> Span {
|
||||
pub(super) fn span(
|
||||
&self,
|
||||
path: &str,
|
||||
line_offset_start: &str,
|
||||
line_offset_end: &str,
|
||||
) -> Span {
|
||||
let span = self.path(path);
|
||||
|
||||
let file = span.expect_ty_file();
|
||||
@@ -2540,7 +2672,7 @@ watermelon
|
||||
}
|
||||
|
||||
/// Like `span`, but only attaches a file path.
|
||||
fn path(&self, path: &str) -> Span {
|
||||
pub(super) fn path(&self, path: &str) -> Span {
|
||||
let file = system_path_to_file(&self.db, path).unwrap();
|
||||
Span::from(file)
|
||||
}
|
||||
@@ -2654,7 +2786,7 @@ watermelon
|
||||
///
|
||||
/// See the docs on `TestEnvironment::span` for the meaning of
|
||||
/// `path`, `line_offset_start` and `line_offset_end`.
|
||||
fn secondary(
|
||||
pub(super) fn secondary(
|
||||
mut self,
|
||||
path: &str,
|
||||
line_offset_start: &str,
|
||||
@@ -2690,7 +2822,7 @@ watermelon
|
||||
}
|
||||
|
||||
/// Adds a "help" sub-diagnostic with the given message.
|
||||
fn help(mut self, message: impl IntoDiagnosticMessage) -> DiagnosticBuilder<'e> {
|
||||
pub(super) fn help(mut self, message: impl IntoDiagnosticMessage) -> DiagnosticBuilder<'e> {
|
||||
self.diag.help(message);
|
||||
self
|
||||
}
|
||||
@@ -2850,10 +2982,10 @@ if call(foo
|
||||
env.format(format);
|
||||
|
||||
let diagnostics = vec![
|
||||
env.invalid_syntax("SyntaxError: Expected one or more symbol names after import")
|
||||
env.invalid_syntax("Expected one or more symbol names after import")
|
||||
.primary("syntax_errors.py", "1:14", "1:15", "")
|
||||
.build(),
|
||||
env.invalid_syntax("SyntaxError: Expected ')', found newline")
|
||||
env.invalid_syntax("Expected ')', found newline")
|
||||
.primary("syntax_errors.py", "3:11", "3:12", "")
|
||||
.build(),
|
||||
];
|
||||
@@ -2861,7 +2993,8 @@ if call(foo
|
||||
(env, diagnostics)
|
||||
}
|
||||
|
||||
/// Create Ruff-style diagnostics for testing the various output formats for a notebook.
|
||||
/// A Jupyter notebook for testing diagnostics.
|
||||
///
|
||||
///
|
||||
/// The concatenated cells look like this:
|
||||
///
|
||||
@@ -2881,17 +3014,7 @@ if call(foo
|
||||
/// The first diagnostic is on the unused `os` import with location cell 1, row 2, column 8
|
||||
/// (`cell 1:2:8`). The second diagnostic is the unused `math` import at `cell 2:2:8`, and the
|
||||
/// third diagnostic is an unfixable unused variable at `cell 3:4:5`.
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "This is currently only used for JSON but will be needed soon for other formats"
|
||||
)]
|
||||
pub(crate) fn create_notebook_diagnostics(
|
||||
format: DiagnosticFormat,
|
||||
) -> (TestEnvironment, Vec<Diagnostic>) {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add(
|
||||
"notebook.ipynb",
|
||||
r##"
|
||||
pub(super) static NOTEBOOK: &str = r##"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
@@ -2930,8 +3053,14 @@ if call(foo
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"##,
|
||||
);
|
||||
"##;
|
||||
|
||||
/// Create Ruff-style diagnostics for testing the various output formats for a notebook.
|
||||
pub(crate) fn create_notebook_diagnostics(
|
||||
format: DiagnosticFormat,
|
||||
) -> (TestEnvironment, Vec<Diagnostic>) {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("notebook.ipynb", NOTEBOOK);
|
||||
env.format(format);
|
||||
|
||||
let diagnostics = vec![
|
||||
|
||||
@@ -50,10 +50,8 @@ impl AzureRenderer<'_> {
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"{code}]{body}",
|
||||
code = diag
|
||||
.secondary_code()
|
||||
.map_or_else(String::new, |code| format!("code={code};")),
|
||||
"code={code};]{body}",
|
||||
code = diag.secondary_code_or_id(),
|
||||
body = diag.body(),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,12 @@ impl<'a> ConciseRenderer<'a> {
|
||||
"{code} ",
|
||||
code = fmt_styled(code, stylesheet.secondary_code)
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{id}: ",
|
||||
id = fmt_styled(diag.inner.id.as_str(), stylesheet.secondary_code)
|
||||
)?;
|
||||
}
|
||||
if self.config.show_fix_status {
|
||||
if let Some(fix) = diag.fix() {
|
||||
@@ -156,8 +162,8 @@ mod tests {
|
||||
env.show_fix_status(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
");
|
||||
}
|
||||
|
||||
@@ -165,8 +171,8 @@ mod tests {
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
syntax_errors.py:1:15: error[invalid-syntax] SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: error[invalid-syntax] SyntaxError: Expected ')', found newline
|
||||
syntax_errors.py:1:15: error[invalid-syntax] Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: error[invalid-syntax] Expected ')', found newline
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat, Severity,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
Annotation, DiagnosticFormat, Severity,
|
||||
render::tests::{
|
||||
NOTEBOOK, TestEnvironment, create_diagnostics, create_notebook_diagnostics,
|
||||
create_syntax_error_diagnostics,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -42,7 +48,7 @@ mod tests {
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[invalid-syntax]: SyntaxError: Expected one or more symbol names after import
|
||||
error[invalid-syntax]: Expected one or more symbol names after import
|
||||
--> syntax_errors.py:1:15
|
||||
|
|
||||
1 | from os import
|
||||
@@ -51,7 +57,71 @@ mod tests {
|
||||
3 | if call(foo
|
||||
|
|
||||
|
||||
error[invalid-syntax]: SyntaxError: Expected ')', found newline
|
||||
error[invalid-syntax]: Expected ')', found newline
|
||||
--> syntax_errors.py:3:12
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
3 | if call(foo
|
||||
| ^
|
||||
4 | def bar():
|
||||
5 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_severity_output() {
|
||||
let (mut env, diagnostics) = create_diagnostics(DiagnosticFormat::Full);
|
||||
env.hide_severity(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r#"
|
||||
F401 [*] `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
F841 [*] Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
F821 Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_severity_syntax_errors() {
|
||||
let (mut env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
|
||||
env.hide_severity(true);
|
||||
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
invalid-syntax: Expected one or more symbol names after import
|
||||
--> syntax_errors.py:1:15
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
2 |
|
||||
3 | if call(foo
|
||||
|
|
||||
|
||||
invalid-syntax: Expected ')', found newline
|
||||
--> syntax_errors.py:3:12
|
||||
|
|
||||
1 | from os import
|
||||
@@ -198,4 +268,136 @@ print()
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// For file-level diagnostics, we expect to see the header line with the diagnostic information
|
||||
/// and the `-->` line with the file information but no lines of source code.
|
||||
#[test]
|
||||
fn file_level() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let mut diagnostic = env.err().build();
|
||||
let span = env.path("example.py").with_range(TextRange::default());
|
||||
let mut annotation = Annotation::primary(span);
|
||||
annotation.set_file_level(true);
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> example.py:1:1
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that ranges in notebooks are remapped relative to the cells.
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let (env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
error[unused-import][*]: `math` imported but unused
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
|
||||
error[unused-variable]: Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
");
|
||||
}
|
||||
|
||||
/// Check notebook handling for multiple annotations in a single diagnostic that span cells.
|
||||
#[test]
|
||||
fn notebook_output_multiple_annotations() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("notebook.ipynb", NOTEBOOK);
|
||||
|
||||
let diagnostics = vec![
|
||||
// adjacent context windows
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "")
|
||||
.secondary("notebook.ipynb", "4:7", "4:11", "second cell")
|
||||
.help("Remove unused import: `os`")
|
||||
.build(),
|
||||
// non-adjacent context windows
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "")
|
||||
.secondary("notebook.ipynb", "10:4", "10:5", "second cell")
|
||||
.help("Remove unused import: `os`")
|
||||
.build(),
|
||||
// adjacent context windows in the same cell
|
||||
env.err()
|
||||
.primary("notebook.ipynb", "4:7", "4:11", "second cell")
|
||||
.secondary("notebook.ipynb", "6:0", "6:5", "print statement")
|
||||
.help("Remove `print` statement")
|
||||
.build(),
|
||||
];
|
||||
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[unused-import]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
::: notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ---- second cell
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
error[unused-import]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
::: notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| - second cell
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^ second cell
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
| ----- print statement
|
||||
|
|
||||
help: Remove `print` statement
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
// In preview, the locations and filename can be optional.
|
||||
if config.preview {
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code(),
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
@@ -111,7 +111,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
}
|
||||
} else {
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code(),
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
@@ -221,7 +221,7 @@ impl Serialize for ExpandedEdits<'_> {
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct JsonDiagnostic<'a> {
|
||||
cell: Option<OneIndexed>,
|
||||
code: Option<&'a SecondaryCode>,
|
||||
code: &'a str,
|
||||
end_location: Option<JsonLocation>,
|
||||
filename: Option<&'a str>,
|
||||
fix: Option<JsonFix<'a>>,
|
||||
@@ -302,7 +302,7 @@ mod tests {
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"code": "test-diagnostic",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 1
|
||||
@@ -336,7 +336,7 @@ mod tests {
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"code": "test-diagnostic",
|
||||
"end_location": null,
|
||||
"filename": null,
|
||||
"fix": null,
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/azure.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;]SyntaxError: Expected one or more symbol names after import
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;]SyntaxError: Expected ')', found newline
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;code=invalid-syntax;]Expected one or more symbol names after import
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;code=invalid-syntax;]Expected ')', found newline
|
||||
|
||||
@@ -5,7 +5,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"code": "invalid-syntax",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 2
|
||||
@@ -16,13 +16,13 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 15,
|
||||
"row": 1
|
||||
},
|
||||
"message": "SyntaxError: Expected one or more symbol names after import",
|
||||
"message": "Expected one or more symbol names after import",
|
||||
"noqa_row": null,
|
||||
"url": null
|
||||
},
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"code": "invalid-syntax",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 4
|
||||
@@ -33,7 +33,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
"column": 12,
|
||||
"row": 3
|
||||
},
|
||||
"message": "SyntaxError: Expected ')', found newline",
|
||||
"message": "Expected ')', found newline",
|
||||
"noqa_row": null,
|
||||
"url": null
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/json_lines.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{"cell":null,"code":null,"end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"SyntaxError: Expected one or more symbol names after import","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":null,"end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"SyntaxError: Expected ')', found newline","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"Expected one or more symbol names after import","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"Expected ')', found newline","noqa_row":null,"url":null}
|
||||
|
||||
@@ -6,10 +6,10 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
<testsuites name="ruff" tests="2" failures="2" errors="0">
|
||||
<testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="1" column="15">
|
||||
<failure message="SyntaxError: Expected one or more symbol names after import">line 1, col 15, SyntaxError: Expected one or more symbol names after import</failure>
|
||||
<failure message="Expected one or more symbol names after import">line 1, col 15, Expected one or more symbol names after import</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="3" column="12">
|
||||
<failure message="SyntaxError: Expected ')', found newline">line 3, col 12, SyntaxError: Expected ')', found newline</failure>
|
||||
<failure message="Expected ')', found newline">line 3, col 12, Expected ')', found newline</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/pylint.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
syntax_errors.py:1: [invalid-syntax] SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3: [invalid-syntax] SyntaxError: Expected ')', found newline
|
||||
syntax_errors.py:1: [invalid-syntax] Expected one or more symbol names after import
|
||||
syntax_errors.py:3: [invalid-syntax] Expected ')', found newline
|
||||
|
||||
@@ -21,7 +21,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "SyntaxError: Expected one or more symbol names after import"
|
||||
"message": "Expected one or more symbol names after import"
|
||||
},
|
||||
{
|
||||
"code": {
|
||||
@@ -40,7 +40,7 @@ expression: env.render_diagnostics(&diagnostics)
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "SyntaxError: Expected ')', found newline"
|
||||
"message": "Expected ')', found newline"
|
||||
}
|
||||
],
|
||||
"severity": "WARNING",
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::source::source_text;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||
/// for determining if a query result is unchanged.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::Db;
|
||||
use crate::files::{File, FilePath};
|
||||
|
||||
/// Reads the source text of a python text file (must be valid UTF8) or notebook.
|
||||
#[salsa::tracked(heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
let path = file.path(db);
|
||||
let _span = tracing::trace_span!("source_text", file = %path).entered();
|
||||
@@ -157,7 +157,7 @@ pub enum SourceTextError {
|
||||
}
|
||||
|
||||
/// Computes the [`LineIndex`] for `file`.
|
||||
#[salsa::tracked(heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
|
||||
let _span = tracing::trace_span!("line_index", ?file).entered();
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ impl SystemPath {
|
||||
///
|
||||
/// [`CurDir`]: camino::Utf8Component::CurDir
|
||||
#[inline]
|
||||
pub fn components(&self) -> camino::Utf8Components {
|
||||
pub fn components(&self) -> camino::Utf8Components<'_> {
|
||||
self.0.components()
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ impl VendoredFileSystem {
|
||||
///
|
||||
/// ## Panics:
|
||||
/// If the current thread already holds the lock.
|
||||
fn lock_archive(&self) -> LockedZipArchive {
|
||||
fn lock_archive(&self) -> LockedZipArchive<'_> {
|
||||
self.inner.lock().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,7 @@ impl VendoredZipArchive {
|
||||
Ok(Self(ZipArchive::new(io::Cursor::new(data))?))
|
||||
}
|
||||
|
||||
fn lookup_path(&mut self, path: &NormalizedVendoredPath) -> Result<ZipFile> {
|
||||
fn lookup_path(&mut self, path: &NormalizedVendoredPath) -> Result<ZipFile<'_>> {
|
||||
Ok(self.0.by_name(path.as_str())?)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ impl VendoredPath {
|
||||
self.0.as_std_path()
|
||||
}
|
||||
|
||||
pub fn components(&self) -> Utf8Components {
|
||||
pub fn components(&self) -> Utf8Components<'_> {
|
||||
self.0.components()
|
||||
}
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ fn format_dev_multi_project(
|
||||
debug!(parent: None, "Starting {}", project_path.display());
|
||||
|
||||
match format_dev_project(
|
||||
&[project_path.clone()],
|
||||
std::slice::from_ref(&project_path),
|
||||
args.stability_check,
|
||||
args.write,
|
||||
args.preview,
|
||||
@@ -628,7 +628,7 @@ struct CheckRepoResult {
|
||||
}
|
||||
|
||||
impl CheckRepoResult {
|
||||
fn display(&self, format: Format) -> DisplayCheckRepoResult {
|
||||
fn display(&self, format: Format) -> DisplayCheckRepoResult<'_> {
|
||||
DisplayCheckRepoResult {
|
||||
result: self,
|
||||
format,
|
||||
@@ -665,7 +665,7 @@ struct Diagnostic {
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
fn display(&self, format: Format) -> DisplayDiagnostic {
|
||||
fn display(&self, format: Format) -> DisplayDiagnostic<'_> {
|
||||
DisplayDiagnostic {
|
||||
diagnostic: self,
|
||||
format,
|
||||
|
||||
@@ -562,7 +562,7 @@ struct RemoveSoftLinebreaksSnapshot {
|
||||
pub trait BufferExtensions: Buffer + Sized {
|
||||
/// Returns a new buffer that calls the passed inspector for every element that gets written to the output
|
||||
#[must_use]
|
||||
fn inspect<F>(&mut self, inspector: F) -> Inspect<Self::Context, F>
|
||||
fn inspect<F>(&mut self, inspector: F) -> Inspect<'_, Self::Context, F>
|
||||
where
|
||||
F: FnMut(&FormatElement),
|
||||
{
|
||||
@@ -607,7 +607,7 @@ pub trait BufferExtensions: Buffer + Sized {
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use]
|
||||
fn start_recording(&mut self) -> Recording<Self> {
|
||||
fn start_recording(&mut self) -> Recording<'_, Self> {
|
||||
Recording::new(self)
|
||||
}
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ impl<Context> Format<Context> for SourcePosition {
|
||||
/// Creates a text from a dynamic string.
|
||||
///
|
||||
/// This is done by allocating a new string internally.
|
||||
pub fn text(text: &str) -> Text {
|
||||
pub fn text(text: &str) -> Text<'_> {
|
||||
debug_assert_no_newlines(text);
|
||||
|
||||
Text { text }
|
||||
@@ -459,7 +459,10 @@ fn debug_assert_no_newlines(text: &str) {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn line_suffix<Content, Context>(inner: &Content, reserved_width: u32) -> LineSuffix<Context>
|
||||
pub fn line_suffix<Content, Context>(
|
||||
inner: &Content,
|
||||
reserved_width: u32,
|
||||
) -> LineSuffix<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -597,7 +600,10 @@ impl<Context> Format<Context> for LineSuffixBoundary {
|
||||
/// Use `Memoized.inspect(f)?.has_label(LabelId::of::<SomeLabelId>()` if you need to know if some content breaks that should
|
||||
/// only be written later.
|
||||
#[inline]
|
||||
pub fn labelled<Content, Context>(label_id: LabelId, content: &Content) -> FormatLabelled<Context>
|
||||
pub fn labelled<Content, Context>(
|
||||
label_id: LabelId,
|
||||
content: &Content,
|
||||
) -> FormatLabelled<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -700,7 +706,7 @@ impl<Context> Format<Context> for Space {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn indent<Content, Context>(content: &Content) -> Indent<Context>
|
||||
pub fn indent<Content, Context>(content: &Content) -> Indent<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -771,7 +777,7 @@ impl<Context> std::fmt::Debug for Indent<'_, Context> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn dedent<Content, Context>(content: &Content) -> Dedent<Context>
|
||||
pub fn dedent<Content, Context>(content: &Content) -> Dedent<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -846,7 +852,7 @@ impl<Context> std::fmt::Debug for Dedent<'_, Context> {
|
||||
///
|
||||
/// This resembles the behaviour of Prettier's `align(Number.NEGATIVE_INFINITY, content)` IR element.
|
||||
#[inline]
|
||||
pub fn dedent_to_root<Content, Context>(content: &Content) -> Dedent<Context>
|
||||
pub fn dedent_to_root<Content, Context>(content: &Content) -> Dedent<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -960,7 +966,7 @@ where
|
||||
///
|
||||
/// - tab indentation: Printer indents the expression with two tabs because the `align` increases the indentation level.
|
||||
/// - space indentation: Printer indents the expression by 4 spaces (one indentation level) **and** 2 spaces for the align.
|
||||
pub fn align<Content, Context>(count: u8, content: &Content) -> Align<Context>
|
||||
pub fn align<Content, Context>(count: u8, content: &Content) -> Align<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -1030,7 +1036,7 @@ impl<Context> std::fmt::Debug for Align<'_, Context> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
||||
pub fn block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<'_, Context> {
|
||||
BlockIndent {
|
||||
content: Argument::new(content),
|
||||
mode: IndentMode::Block,
|
||||
@@ -1101,7 +1107,7 @@ pub fn block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Cont
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn soft_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
||||
pub fn soft_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<'_, Context> {
|
||||
BlockIndent {
|
||||
content: Argument::new(content),
|
||||
mode: IndentMode::Soft,
|
||||
@@ -1175,7 +1181,9 @@ pub fn soft_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn soft_line_indent_or_space<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
||||
pub fn soft_line_indent_or_space<Context>(
|
||||
content: &impl Format<Context>,
|
||||
) -> BlockIndent<'_, Context> {
|
||||
BlockIndent {
|
||||
content: Argument::new(content),
|
||||
mode: IndentMode::SoftLineOrSpace,
|
||||
@@ -1308,7 +1316,9 @@ impl<Context> std::fmt::Debug for BlockIndent<'_, Context> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn soft_space_or_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
||||
pub fn soft_space_or_block_indent<Context>(
|
||||
content: &impl Format<Context>,
|
||||
) -> BlockIndent<'_, Context> {
|
||||
BlockIndent {
|
||||
content: Argument::new(content),
|
||||
mode: IndentMode::SoftSpace,
|
||||
@@ -1388,7 +1398,7 @@ pub fn soft_space_or_block_indent<Context>(content: &impl Format<Context>) -> Bl
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn group<Context>(content: &impl Format<Context>) -> Group<Context> {
|
||||
pub fn group<Context>(content: &impl Format<Context>) -> Group<'_, Context> {
|
||||
Group {
|
||||
content: Argument::new(content),
|
||||
id: None,
|
||||
@@ -1551,7 +1561,7 @@ impl<Context> std::fmt::Debug for Group<'_, Context> {
|
||||
#[inline]
|
||||
pub fn best_fit_parenthesize<Context>(
|
||||
content: &impl Format<Context>,
|
||||
) -> BestFitParenthesize<Context> {
|
||||
) -> BestFitParenthesize<'_, Context> {
|
||||
BestFitParenthesize {
|
||||
content: Argument::new(content),
|
||||
group_id: None,
|
||||
@@ -1691,7 +1701,7 @@ impl<Context> std::fmt::Debug for BestFitParenthesize<'_, Context> {
|
||||
pub fn conditional_group<Content, Context>(
|
||||
content: &Content,
|
||||
condition: Condition,
|
||||
) -> ConditionalGroup<Context>
|
||||
) -> ConditionalGroup<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -1852,7 +1862,7 @@ impl<Context> Format<Context> for ExpandParent {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn if_group_breaks<Content, Context>(content: &Content) -> IfGroupBreaks<Context>
|
||||
pub fn if_group_breaks<Content, Context>(content: &Content) -> IfGroupBreaks<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -1933,7 +1943,7 @@ where
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn if_group_fits_on_line<Content, Context>(flat_content: &Content) -> IfGroupBreaks<Context>
|
||||
pub fn if_group_fits_on_line<Content, Context>(flat_content: &Content) -> IfGroupBreaks<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -2122,7 +2132,7 @@ impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
|
||||
pub fn indent_if_group_breaks<Content, Context>(
|
||||
content: &Content,
|
||||
group_id: GroupId,
|
||||
) -> IndentIfGroupBreaks<Context>
|
||||
) -> IndentIfGroupBreaks<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
@@ -2205,7 +2215,7 @@ impl<Context> std::fmt::Debug for IndentIfGroupBreaks<'_, Context> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn fits_expanded<Content, Context>(content: &Content) -> FitsExpanded<Context>
|
||||
pub fn fits_expanded<Content, Context>(content: &Content) -> FitsExpanded<'_, Context>
|
||||
where
|
||||
Content: Format<Context>,
|
||||
{
|
||||
|
||||
@@ -197,7 +197,7 @@ pub const LINE_TERMINATORS: [char; 3] = ['\r', LINE_SEPARATOR, PARAGRAPH_SEPARAT
|
||||
|
||||
/// Replace the line terminators matching the provided list with "\n"
|
||||
/// since its the only line break type supported by the printer
|
||||
pub fn normalize_newlines<const N: usize>(text: &str, terminators: [char; N]) -> Cow<str> {
|
||||
pub fn normalize_newlines<const N: usize>(text: &str, terminators: [char; N]) -> Cow<'_, str> {
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ impl FormatContext for IrFormatContext<'_> {
|
||||
&IrFormatOptions
|
||||
}
|
||||
|
||||
fn source_code(&self) -> SourceCode {
|
||||
fn source_code(&self) -> SourceCode<'_> {
|
||||
self.source_code
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ pub trait FormatContext {
|
||||
fn options(&self) -> &Self::Options;
|
||||
|
||||
/// Returns the source code from the document that gets formatted.
|
||||
fn source_code(&self) -> SourceCode;
|
||||
fn source_code(&self) -> SourceCode<'_>;
|
||||
}
|
||||
|
||||
/// Options customizing how the source code should be formatted.
|
||||
@@ -239,7 +239,7 @@ impl FormatContext for SimpleFormatContext {
|
||||
&self.options
|
||||
}
|
||||
|
||||
fn source_code(&self) -> SourceCode {
|
||||
fn source_code(&self) -> SourceCode<'_> {
|
||||
SourceCode::new(&self.source_code)
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ where
|
||||
printer.print_with_indent(&self.document, indent)
|
||||
}
|
||||
|
||||
fn create_printer(&self) -> Printer {
|
||||
fn create_printer(&self) -> Printer<'_> {
|
||||
let source_code = self.context.source_code();
|
||||
let print_options = self.context.options().as_print_options();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -182,3 +182,13 @@ kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
|
||||
## Test unpacked list literal args (starred expressions)
|
||||
# Errors
|
||||
"1,2,3".split(",", *[-1])[0]
|
||||
|
||||
## Test unpacked list variable args
|
||||
# Errors
|
||||
args_list = [-1]
|
||||
"1,2,3".split(",", *args_list)[0]
|
||||
|
||||
@@ -315,7 +315,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
/// Create a [`Generator`] to generate source code based on the current AST state.
|
||||
pub(crate) fn generator(&self) -> Generator {
|
||||
pub(crate) fn generator(&self) -> Generator<'_> {
|
||||
Generator::new(self.stylist.indentation(), self.stylist.line_ending())
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ use libcst_native::{
|
||||
};
|
||||
use ruff_python_codegen::Stylist;
|
||||
|
||||
pub(crate) fn match_module(module_text: &str) -> Result<Module> {
|
||||
pub(crate) fn match_module(module_text: &str) -> Result<Module<'_>> {
|
||||
match libcst_native::parse_module(module_text, None) {
|
||||
Ok(module) => Ok(module),
|
||||
Err(_) => bail!("Failed to extract CST from source"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_statement(statement_text: &str) -> Result<Statement> {
|
||||
pub(crate) fn match_statement(statement_text: &str) -> Result<Statement<'_>> {
|
||||
match libcst_native::parse_statement(statement_text) {
|
||||
Ok(statement) => Ok(statement),
|
||||
Err(_) => bail!("Failed to extract statement from source"),
|
||||
@@ -220,7 +220,7 @@ pub(crate) fn match_if<'a, 'b>(statement: &'a mut Statement<'b>) -> Result<&'a m
|
||||
///
|
||||
/// If the expression is not guaranteed to be valid as a standalone expression (e.g., if it may
|
||||
/// span multiple lines and/or require parentheses), use [`transform_expression`] instead.
|
||||
pub(crate) fn match_expression(expression_text: &str) -> Result<Expression> {
|
||||
pub(crate) fn match_expression(expression_text: &str) -> Result<Expression<'_>> {
|
||||
match libcst_native::parse_expression(expression_text) {
|
||||
Ok(expression) => Ok(expression),
|
||||
Err(_) => bail!("Failed to extract expression from source"),
|
||||
|
||||
@@ -13,7 +13,7 @@ use ruff_text_size::{Ranged, TextSize};
|
||||
use crate::Locator;
|
||||
|
||||
/// Extract doc lines (standalone comments) from a token sequence.
|
||||
pub(crate) fn doc_lines_from_tokens(tokens: &Tokens) -> DocLines {
|
||||
pub(crate) fn doc_lines_from_tokens(tokens: &Tokens) -> DocLines<'_> {
|
||||
DocLines::new(tokens)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ impl<'a> Docstring<'a> {
|
||||
}
|
||||
|
||||
/// The contents of the docstring, excluding the opening and closing quotes.
|
||||
pub(crate) fn body(&self) -> DocstringBody {
|
||||
pub(crate) fn body(&self) -> DocstringBody<'_> {
|
||||
DocstringBody { docstring: self }
|
||||
}
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ impl<'a> SectionContexts<'a> {
|
||||
self.contexts.len()
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> SectionContextsIter {
|
||||
pub(crate) fn iter(&self) -> SectionContextsIter<'_> {
|
||||
SectionContextsIter {
|
||||
docstring_body: self.docstring.body(),
|
||||
inner: self.contexts.iter(),
|
||||
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
fn insert(contents: &str) -> Result<Insertion<'_>> {
|
||||
let parsed = parse_module(contents)?;
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents());
|
||||
@@ -450,7 +450,7 @@ x = 1
|
||||
|
||||
#[test]
|
||||
fn start_of_block() {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion<'_> {
|
||||
let parsed = parse_module(contents).unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents());
|
||||
|
||||
@@ -49,7 +49,7 @@ impl<'a> Locator<'a> {
|
||||
self.index.get()
|
||||
}
|
||||
|
||||
pub fn to_source_code(&self) -> SourceCode {
|
||||
pub fn to_source_code(&self) -> SourceCode<'_, '_> {
|
||||
SourceCode::new(self.contents, self.to_index())
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,8 @@ impl Emitter for GithubEmitter {
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = diagnostic
|
||||
.secondary_code()
|
||||
.map_or_else(String::new, |code| format!(" ({code})")),
|
||||
"::error title=Ruff ({code}),file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = diagnostic.secondary_code_or_id(),
|
||||
file = filename,
|
||||
row = source_location.line,
|
||||
column = source_location.column,
|
||||
@@ -54,6 +52,8 @@ impl Emitter for GithubEmitter {
|
||||
|
||||
if let Some(code) = diagnostic.secondary_code() {
|
||||
write!(writer, " {code}")?;
|
||||
} else {
|
||||
write!(writer, " {id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(writer, " {}", diagnostic.body())?;
|
||||
|
||||
@@ -33,8 +33,7 @@ mod text;
|
||||
/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff.
|
||||
///
|
||||
/// This is almost identical to `ruff_db::diagnostic::create_syntax_error_diagnostic`, except the
|
||||
/// `message` is stored as the primary diagnostic message instead of on the primary annotation, and
|
||||
/// `SyntaxError: ` is prepended to the message.
|
||||
/// `message` is stored as the primary diagnostic message instead of on the primary annotation.
|
||||
///
|
||||
/// TODO(brent) These should be unified at some point, but we keep them separate for now to avoid a
|
||||
/// ton of snapshot changes while combining ruff's diagnostic type with `Diagnostic`.
|
||||
@@ -43,11 +42,7 @@ pub fn create_syntax_error_diagnostic(
|
||||
message: impl std::fmt::Display,
|
||||
range: impl Ranged,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(
|
||||
DiagnosticId::InvalidSyntax,
|
||||
Severity::Error,
|
||||
format_args!("SyntaxError: {message}"),
|
||||
);
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
|
||||
let span = span.into().with_range(range.range());
|
||||
diag.annotate(Annotation::primary(span));
|
||||
diag
|
||||
@@ -75,7 +70,15 @@ where
|
||||
);
|
||||
|
||||
let span = Span::from(file).with_range(range);
|
||||
let annotation = Annotation::primary(span);
|
||||
let mut annotation = Annotation::primary(span);
|
||||
// The `0..0` range is used to highlight file-level diagnostics.
|
||||
//
|
||||
// TODO(brent) We should instead set this flag on annotations for individual lint rules that
|
||||
// actually need it, but we need to be able to cache the new diagnostic model first. See
|
||||
// https://github.com/astral-sh/ruff/issues/19688.
|
||||
if range == TextRange::default() {
|
||||
annotation.set_file_level(true);
|
||||
}
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
if let Some(suggestion) = suggestion {
|
||||
@@ -146,7 +149,7 @@ impl Deref for MessageWithLocation<'_> {
|
||||
|
||||
fn group_diagnostics_by_filename(
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> BTreeMap<String, Vec<MessageWithLocation>> {
|
||||
) -> BTreeMap<String, Vec<MessageWithLocation<'_>>> {
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for diagnostic in diagnostics {
|
||||
grouped_messages
|
||||
|
||||
@@ -27,7 +27,10 @@ impl Emitter for SarifEmitter {
|
||||
.map(SarifResult::from_message)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let unique_rules: HashSet<_> = results.iter().filter_map(|result| result.code).collect();
|
||||
let unique_rules: HashSet<_> = results
|
||||
.iter()
|
||||
.filter_map(|result| result.code.as_secondary_code())
|
||||
.collect();
|
||||
let mut rules: Vec<SarifRule> = unique_rules.into_iter().map(SarifRule::from).collect();
|
||||
rules.sort_by(|a, b| a.code.cmp(b.code));
|
||||
|
||||
@@ -109,9 +112,40 @@ impl Serialize for SarifRule<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RuleCode<'a> {
|
||||
SecondaryCode(&'a SecondaryCode),
|
||||
LintId(&'a str),
|
||||
}
|
||||
|
||||
impl RuleCode<'_> {
|
||||
fn as_secondary_code(&self) -> Option<&SecondaryCode> {
|
||||
match self {
|
||||
RuleCode::SecondaryCode(code) => Some(code),
|
||||
RuleCode::LintId(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
RuleCode::SecondaryCode(code) => code.as_str(),
|
||||
RuleCode::LintId(id) => id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Diagnostic> for RuleCode<'a> {
|
||||
fn from(code: &'a Diagnostic) -> Self {
|
||||
match code.secondary_code() {
|
||||
Some(diagnostic) => Self::SecondaryCode(diagnostic),
|
||||
None => Self::LintId(code.id().as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SarifResult<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
code: RuleCode<'a>,
|
||||
level: String,
|
||||
message: String,
|
||||
uri: String,
|
||||
@@ -128,7 +162,7 @@ impl<'a> SarifResult<'a> {
|
||||
let end_location = message.expect_ruff_end_location();
|
||||
let path = normalize_path(&*message.expect_ruff_filename());
|
||||
Ok(Self {
|
||||
code: message.secondary_code(),
|
||||
code: RuleCode::from(message),
|
||||
level: "error".to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: url::Url::from_file_path(&path)
|
||||
@@ -148,7 +182,7 @@ impl<'a> SarifResult<'a> {
|
||||
let end_location = message.expect_ruff_end_location();
|
||||
let path = normalize_path(&*message.expect_ruff_filename());
|
||||
Ok(Self {
|
||||
code: message.secondary_code(),
|
||||
code: RuleCode::from(message),
|
||||
level: "error".to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: path.display().to_string(),
|
||||
@@ -183,7 +217,7 @@ impl Serialize for SarifResult<'_> {
|
||||
}
|
||||
}
|
||||
}],
|
||||
"ruleId": self.code,
|
||||
"ruleId": self.code.as_str(),
|
||||
})
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/github.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
---
|
||||
::error title=Ruff,file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
::error title=Ruff,file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/grouped.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
---
|
||||
syntax_errors.py:
|
||||
1:15 SyntaxError: Expected one or more symbol names after import
|
||||
3:12 SyntaxError: Expected ')', found newline
|
||||
1:15 invalid-syntax: Expected one or more symbol names after import
|
||||
3:12 invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
@@ -11,7 +11,7 @@ syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after impo
|
||||
4 | def bar():
|
||||
|
|
||||
|
||||
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
|
||||
@@ -154,7 +154,12 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
body = self.message.body(),
|
||||
)
|
||||
} else {
|
||||
f.write_str(self.message.body())
|
||||
write!(
|
||||
f,
|
||||
"{code}: {body}",
|
||||
code = self.message.id().as_str().red().bold(),
|
||||
body = self.message.body(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +286,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
/// modify the annotation ranges by inserting 3-byte Unicode replacements
|
||||
/// because `annotate-snippets` will account for their actual width when
|
||||
/// rendering and displaying the column to the user.
|
||||
fn replace_unprintable(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
fn replace_unprintable(source: &str, annotation_range: TextRange) -> SourceCode<'_> {
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) struct Codes<'a> {
|
||||
|
||||
impl Codes<'_> {
|
||||
/// Returns an iterator over the [`Code`]s in the `noqa` directive.
|
||||
pub(crate) fn iter(&self) -> std::slice::Iter<Code> {
|
||||
pub(crate) fn iter(&self) -> std::slice::Iter<'_, Code<'_>> {
|
||||
self.codes.iter()
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
Self(lines)
|
||||
}
|
||||
|
||||
pub(crate) fn lines(&self) -> &[FileNoqaDirectiveLine] {
|
||||
pub(crate) fn lines(&self) -> &[FileNoqaDirectiveLine<'_>] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@@ -1106,7 +1106,10 @@ impl<'a> NoqaDirectives<'a> {
|
||||
Self { inner: directives }
|
||||
}
|
||||
|
||||
pub(crate) fn find_line_with_directive(&self, offset: TextSize) -> Option<&NoqaDirectiveLine> {
|
||||
pub(crate) fn find_line_with_directive(
|
||||
&self,
|
||||
offset: TextSize,
|
||||
) -> Option<&NoqaDirectiveLine<'_>> {
|
||||
self.find_line_index(offset).map(|index| &self.inner[index])
|
||||
}
|
||||
|
||||
@@ -1139,7 +1142,7 @@ impl<'a> NoqaDirectives<'a> {
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn lines(&self) -> &[NoqaDirectiveLine] {
|
||||
pub(crate) fn lines(&self) -> &[NoqaDirectiveLine<'_>] {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
COM81_syntax_error.py:3:5: invalid-syntax: Starred expression cannot be used here
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
@@ -10,7 +10,7 @@ COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
4 | )
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
||||
COM81_syntax_error.py:6:9: invalid-syntax: Type parameter list cannot be empty
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
COM81_syntax_error.py:3:5: invalid-syntax: Starred expression cannot be used here
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
@@ -10,7 +10,7 @@ COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
4 | )
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
||||
COM81_syntax_error.py:6:9: invalid-syntax: Type parameter list cannot be empty
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
||||
---
|
||||
ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:2:5: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -10,7 +10,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
|
||||
4 | "a" """b
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:2:7: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:2:7: invalid-syntax: Expected a statement
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -31,7 +31,7 @@ ISC_syntax_error.py:3:1: ISC001 Implicitly concatenated string literals on one l
|
||||
|
|
||||
= help: Combine string literals
|
||||
|
||||
ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:3:9: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -41,7 +41,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:3:11: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:3:11: invalid-syntax: Expected a statement
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -63,7 +63,7 @@ ISC_syntax_error.py:4:1: ISC001 Implicitly concatenated string literals on one l
|
||||
|
|
||||
= help: Combine string literals
|
||||
|
||||
ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:5:6: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
@@ -73,7 +73,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:5:8: invalid-syntax: Expected a statement
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
@@ -84,7 +84,7 @@ ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
|
||||
8 | # unterminated f-strings.
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:9:8: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
8 | # unterminated f-strings.
|
||||
@@ -94,7 +94,7 @@ ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
|
||||
11 | f"a" f"""b
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline
|
||||
ISC_syntax_error.py:9:9: invalid-syntax: Expected FStringEnd, found newline
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
8 | # unterminated f-strings.
|
||||
@@ -116,7 +116,7 @@ ISC_syntax_error.py:10:1: ISC001 Implicitly concatenated string literals on one
|
||||
|
|
||||
= help: Combine string literals
|
||||
|
||||
ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:10:13: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
@@ -126,7 +126,7 @@ ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
|
||||
12 | c""" f"d {e
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline
|
||||
ISC_syntax_error.py:10:14: invalid-syntax: Expected FStringEnd, found newline
|
||||
|
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
@@ -148,7 +148,7 @@ ISC_syntax_error.py:11:1: ISC001 Implicitly concatenated string literals on one
|
||||
|
|
||||
= help: Combine string literals
|
||||
|
||||
ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:16:5: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
14 | (
|
||||
15 | "a"
|
||||
@@ -158,7 +158,7 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
|
||||
18 | "d"
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string
|
||||
ISC_syntax_error.py:26:9: invalid-syntax: f-string: unterminated triple-quoted string
|
||||
|
|
||||
24 | (
|
||||
25 | """abc"""
|
||||
@@ -170,14 +170,14 @@ ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted stri
|
||||
| |__^
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing
|
||||
ISC_syntax_error.py:30:1: invalid-syntax: unexpected EOF while parsing
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
| ^
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:30:1: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:30:1: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
||||
---
|
||||
ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:2:5: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -10,7 +10,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
|
||||
4 | "a" """b
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:2:7: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:2:7: invalid-syntax: Expected a statement
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -20,7 +20,7 @@ ISC_syntax_error.py:2:7: SyntaxError: Expected a statement
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:3:9: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -30,7 +30,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:3:11: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:3:11: invalid-syntax: Expected a statement
|
||||
|
|
||||
1 | # The lexer doesn't emit a string token if it's unterminated
|
||||
2 | "a" "b
|
||||
@@ -40,7 +40,7 @@ ISC_syntax_error.py:3:11: SyntaxError: Expected a statement
|
||||
5 | c""" "d
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:5:6: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
@@ -50,7 +50,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
|
||||
ISC_syntax_error.py:5:8: invalid-syntax: Expected a statement
|
||||
|
|
||||
3 | "a" "b" "c
|
||||
4 | "a" """b
|
||||
@@ -61,7 +61,7 @@ ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
|
||||
8 | # unterminated f-strings.
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:9:8: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
8 | # unterminated f-strings.
|
||||
@@ -71,7 +71,7 @@ ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
|
||||
11 | f"a" f"""b
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline
|
||||
ISC_syntax_error.py:9:9: invalid-syntax: Expected FStringEnd, found newline
|
||||
|
|
||||
7 | # For f-strings, the `FStringRanges` won't contain the range for
|
||||
8 | # unterminated f-strings.
|
||||
@@ -82,7 +82,7 @@ ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline
|
||||
12 | c""" f"d {e
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:10:13: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
@@ -92,7 +92,7 @@ ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
|
||||
12 | c""" f"d {e
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline
|
||||
ISC_syntax_error.py:10:14: invalid-syntax: Expected FStringEnd, found newline
|
||||
|
|
||||
8 | # unterminated f-strings.
|
||||
9 | f"a" f"b
|
||||
@@ -102,7 +102,7 @@ ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline
|
||||
12 | c""" f"d {e
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
|
||||
ISC_syntax_error.py:16:5: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
14 | (
|
||||
15 | "a"
|
||||
@@ -112,7 +112,7 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
|
||||
18 | "d"
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string
|
||||
ISC_syntax_error.py:26:9: invalid-syntax: f-string: unterminated triple-quoted string
|
||||
|
|
||||
24 | (
|
||||
25 | """abc"""
|
||||
@@ -124,14 +124,14 @@ ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted stri
|
||||
| |__^
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing
|
||||
ISC_syntax_error.py:30:1: invalid-syntax: unexpected EOF while parsing
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
| ^
|
||||
|
|
||||
|
||||
ISC_syntax_error.py:30:1: SyntaxError: f-string: unterminated string
|
||||
ISC_syntax_error.py:30:1: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
28 | "i" "j"
|
||||
29 | )
|
||||
|
||||
@@ -178,7 +178,7 @@ impl<'a> From<&NestedIf<'a>> for AnyNodeRef<'a> {
|
||||
}
|
||||
|
||||
/// Returns the body, the range of the `if` or `elif` and whether the range is for an `if` or `elif`
|
||||
fn nested_if_body(stmt_if: &ast::StmtIf) -> Option<NestedIf> {
|
||||
fn nested_if_body(stmt_if: &ast::StmtIf) -> Option<NestedIf<'_>> {
|
||||
let ast::StmtIf {
|
||||
test,
|
||||
body,
|
||||
|
||||
@@ -267,7 +267,7 @@ struct Terminal<'a> {
|
||||
stmt: &'a Stmt,
|
||||
}
|
||||
|
||||
fn match_loop(stmt: &Stmt) -> Option<Loop> {
|
||||
fn match_loop(stmt: &Stmt) -> Option<Loop<'_>> {
|
||||
let Stmt::For(ast::StmtFor {
|
||||
body, target, iter, ..
|
||||
}) = stmt
|
||||
@@ -324,7 +324,7 @@ fn match_loop(stmt: &Stmt) -> Option<Loop> {
|
||||
/// return True
|
||||
/// return False
|
||||
/// ```
|
||||
fn match_else_return(stmt: &Stmt) -> Option<Terminal> {
|
||||
fn match_else_return(stmt: &Stmt) -> Option<Terminal<'_>> {
|
||||
let Stmt::For(ast::StmtFor { orelse, .. }) = stmt else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -388,7 +388,7 @@ impl KnownModules {
|
||||
|
||||
/// Return the [`ImportSection`] for a given module, if it's been categorized as a known module
|
||||
/// by the user.
|
||||
fn categorize(&self, module_name: &str) -> Option<(&ImportSection, Reason)> {
|
||||
fn categorize(&self, module_name: &str) -> Option<(&ImportSection, Reason<'_>)> {
|
||||
if self.has_submodules {
|
||||
// Check all module prefixes from the longest to the shortest (e.g., given
|
||||
// `foo.bar.baz`, check `foo.bar.baz`, then `foo.bar`, then `foo`, taking the first,
|
||||
@@ -412,7 +412,7 @@ impl KnownModules {
|
||||
}
|
||||
}
|
||||
|
||||
fn categorize_submodule(&self, submodule: &str) -> Option<(&ImportSection, Reason)> {
|
||||
fn categorize_submodule(&self, submodule: &str) -> Option<(&ImportSection, Reason<'_>)> {
|
||||
let section = self.known.iter().find_map(|(pattern, section)| {
|
||||
if pattern.matches(submodule) {
|
||||
Some(section)
|
||||
|
||||
@@ -21,7 +21,7 @@ E11.py:6:1: E111 Indentation is not a multiple of 4
|
||||
8 | if False:
|
||||
|
|
||||
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -31,7 +31,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -41,7 +41,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -51,7 +51,7 @@ E11.py:14:1: SyntaxError: Expected a statement
|
||||
16 | create_date = False
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -11,7 +11,7 @@ E11.py:9:1: E112 Expected an indented block
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -21,7 +21,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -51,7 +51,7 @@ E11.py:45:1: E112 Expected an indented block
|
||||
47 | if False:
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -21,7 +21,7 @@ E11.py:12:1: E113 Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -41,7 +41,7 @@ E11.py:14:1: SyntaxError: Expected a statement
|
||||
16 | create_date = False
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -41,7 +41,7 @@ E11.py:15:1: E114 Indentation is not a multiple of 4 (comment)
|
||||
17 | #: E116 E116 E116
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -91,7 +91,7 @@ E11.py:35:1: E115 Expected an indented block (comment)
|
||||
37 | #: E117
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -71,7 +71,7 @@ E11.py:26:1: E116 Unexpected indentation (comment)
|
||||
28 | def start(self):
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -11,7 +11,7 @@ E11.py:6:1: E117 Over-indented
|
||||
8 | if False:
|
||||
|
|
||||
|
||||
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
7 | #: E112
|
||||
8 | if False:
|
||||
@@ -21,7 +21,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
||||
11 | print()
|
||||
|
|
||||
|
||||
E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
E11.py:12:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
10 | #: E113
|
||||
11 | print()
|
||||
@@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
|
||||
14 | mimetype = 'application/x-directory'
|
||||
|
|
||||
|
||||
E11.py:14:1: SyntaxError: Expected a statement
|
||||
E11.py:14:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
12 | print()
|
||||
13 | #: E114 E116
|
||||
@@ -61,7 +61,7 @@ E11.py:42:1: E117 Over-indented
|
||||
44 | if False: #
|
||||
|
|
||||
|
||||
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
|
||||
E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
|
||||
|
|
||||
43 | #: E112
|
||||
44 | if False: #
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
|
||||
|
|
||||
2 | # parenthesis.
|
||||
3 |
|
||||
@@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
5 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
12 | class Foo:
|
||||
13 | def __init__(
|
||||
@@ -30,7 +30,7 @@ E30_syntax_error.py:15:5: E301 Expected 1 blank line, found 0
|
||||
|
|
||||
= help: Add missing blank line
|
||||
|
||||
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
16 | pass
|
||||
17 |
|
||||
@@ -41,7 +41,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
21 | def top(
|
||||
|
|
||||
|
||||
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
21 | def top(
|
||||
| ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
|
||||
|
|
||||
2 | # parenthesis.
|
||||
3 |
|
||||
@@ -20,7 +20,7 @@ E30_syntax_error.py:7:1: E302 Expected 2 blank lines, found 1
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
12 | class Foo:
|
||||
13 | def __init__(
|
||||
@@ -30,7 +30,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
16 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
16 | pass
|
||||
17 |
|
||||
@@ -41,7 +41,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
21 | def top(
|
||||
|
|
||||
|
||||
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
21 | def top(
|
||||
| ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
|
||||
|
|
||||
2 | # parenthesis.
|
||||
3 |
|
||||
@@ -19,7 +19,7 @@ E30_syntax_error.py:12:1: E303 Too many blank lines (3)
|
||||
|
|
||||
= help: Remove extraneous blank line(s)
|
||||
|
||||
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
12 | class Foo:
|
||||
13 | def __init__(
|
||||
@@ -29,7 +29,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
16 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
16 | pass
|
||||
17 |
|
||||
@@ -40,7 +40,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
21 | def top(
|
||||
|
|
||||
|
||||
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
21 | def top(
|
||||
| ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
|
||||
|
|
||||
2 | # parenthesis.
|
||||
3 |
|
||||
@@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
5 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
12 | class Foo:
|
||||
13 | def __init__(
|
||||
@@ -29,7 +29,7 @@ E30_syntax_error.py:18:1: E305 Expected 2 blank lines after class or function de
|
||||
|
|
||||
= help: Add missing blank line(s)
|
||||
|
||||
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
16 | pass
|
||||
17 |
|
||||
@@ -40,7 +40,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
21 | def top(
|
||||
|
|
||||
|
||||
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
21 | def top(
|
||||
| ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
|
||||
|
|
||||
2 | # parenthesis.
|
||||
3 |
|
||||
@@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
|
||||
5 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
12 | class Foo:
|
||||
13 | def __init__(
|
||||
@@ -20,7 +20,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
|
||||
16 | pass
|
||||
|
|
||||
|
||||
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
16 | pass
|
||||
17 |
|
||||
@@ -31,7 +31,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
|
||||
21 | def top(
|
||||
|
|
||||
|
||||
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline
|
||||
E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
|
||||
|
|
||||
21 | def top(
|
||||
| ^
|
||||
|
||||
Binary file not shown.
@@ -8,14 +8,14 @@ W19.py:1:1: W191 Indentation contains tabs
|
||||
2 | multiline string with tab in it'''
|
||||
|
|
||||
|
||||
W19.py:1:1: SyntaxError: Unexpected indentation
|
||||
W19.py:1:1: invalid-syntax: Unexpected indentation
|
||||
|
|
||||
1 | '''File starts with a tab
|
||||
| ^^^^
|
||||
2 | multiline string with tab in it'''
|
||||
|
|
||||
|
||||
W19.py:5:1: SyntaxError: Expected a statement
|
||||
W19.py:5:1: invalid-syntax: Expected a statement
|
||||
|
|
||||
4 | #: W191
|
||||
5 | if False:
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
E2_syntax_error.py:1:10: SyntaxError: Expected an expression
|
||||
E2_syntax_error.py:1:10: invalid-syntax: Expected an expression
|
||||
|
|
||||
1 | a = (1 or)
|
||||
| ^
|
||||
|
||||
@@ -452,7 +452,7 @@ impl<'a> DocstringSections<'a> {
|
||||
///
|
||||
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
|
||||
/// entries are found.
|
||||
fn parse_entries(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName> {
|
||||
fn parse_entries(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName<'_>> {
|
||||
match style {
|
||||
Some(SectionStyle::Google) => parse_entries_google(content),
|
||||
Some(SectionStyle::Numpy) => parse_entries_numpy(content),
|
||||
@@ -474,7 +474,7 @@ fn parse_entries(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedNam
|
||||
/// FasterThanLightError: If speed is greater than the speed of light.
|
||||
/// DivisionByZero: If attempting to divide by zero.
|
||||
/// ```
|
||||
fn parse_entries_google(content: &str) -> Vec<QualifiedName> {
|
||||
fn parse_entries_google(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
let mut entries: Vec<QualifiedName> = Vec::new();
|
||||
for potential in content.lines() {
|
||||
let Some(colon_idx) = potential.find(':') else {
|
||||
@@ -496,7 +496,7 @@ fn parse_entries_google(content: &str) -> Vec<QualifiedName> {
|
||||
/// DivisionByZero
|
||||
/// If attempting to divide by zero.
|
||||
/// ```
|
||||
fn parse_entries_numpy(content: &str) -> Vec<QualifiedName> {
|
||||
fn parse_entries_numpy(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
let mut entries: Vec<QualifiedName> = Vec::new();
|
||||
let mut lines = content.lines();
|
||||
let Some(dashes) = lines.next() else {
|
||||
|
||||
@@ -177,7 +177,6 @@ pub(crate) fn multi_line_summary_start(checker: &Checker, docstring: &Docstring)
|
||||
// "\
|
||||
// "
|
||||
// ```
|
||||
return;
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::MultiLineSummarySecondLine) {
|
||||
let mut diagnostic =
|
||||
|
||||
@@ -98,11 +98,11 @@ impl Settings {
|
||||
self.convention
|
||||
}
|
||||
|
||||
pub fn ignore_decorators(&self) -> DecoratorIterator {
|
||||
pub fn ignore_decorators(&self) -> DecoratorIterator<'_> {
|
||||
DecoratorIterator::new(&self.ignore_decorators)
|
||||
}
|
||||
|
||||
pub fn property_decorators(&self) -> DecoratorIterator {
|
||||
pub fn property_decorators(&self) -> DecoratorIterator<'_> {
|
||||
DecoratorIterator::new(&self.property_decorators)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `**kwargs`, as
|
||||
/// adding a `maxsplit` keyword to such a call may lead to a duplicate keyword argument error.
|
||||
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `*args` or `**kwargs` arguments, as
|
||||
/// adding a `maxsplit` argument to such a call may lead to duplicated arguments.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingMaxsplitArg {
|
||||
actual_split_type: String,
|
||||
@@ -201,11 +201,12 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
diagnostic.set_fix(Fix::applicable_edits(
|
||||
maxsplit_argument_edit,
|
||||
split_type_edit,
|
||||
// If keyword.arg is `None` (i.e. if the function call contains `**kwargs`), mark the fix as unsafe
|
||||
if arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
// Mark the fix as unsafe, if there are `*args` or `**kwargs`
|
||||
if arguments.args.iter().any(Expr::is_starred_expr)
|
||||
|| arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Ranged for AttributeAssignment<'_> {
|
||||
/// Return a list of attributes that are assigned to but not included in `__slots__`.
|
||||
///
|
||||
/// If the `__slots__` attribute cannot be statically determined, returns an empty vector.
|
||||
fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
|
||||
fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment<'_>> {
|
||||
// First, collect all the attributes that are assigned to `__slots__`.
|
||||
let mut slots = FxHashSet::default();
|
||||
for statement in body {
|
||||
|
||||
@@ -115,7 +115,7 @@ fn check_super_slots(checker: &Checker, class_def: &ast::StmtClassDef, slot: &Sl
|
||||
}
|
||||
}
|
||||
|
||||
fn slots_members(body: &[Stmt]) -> FxHashSet<Slot> {
|
||||
fn slots_members(body: &[Stmt]) -> FxHashSet<Slot<'_>> {
|
||||
let mut members = FxHashSet::default();
|
||||
for stmt in body {
|
||||
match stmt {
|
||||
@@ -161,7 +161,7 @@ fn slots_members(body: &[Stmt]) -> FxHashSet<Slot> {
|
||||
members
|
||||
}
|
||||
|
||||
fn slots_attributes(expr: &Expr) -> impl Iterator<Item = Slot> {
|
||||
fn slots_attributes(expr: &Expr) -> impl Iterator<Item = Slot<'_>> {
|
||||
// Ex) `__slots__ = ("name",)`
|
||||
let elts_iter = match expr {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
|
||||
@@ -676,6 +676,7 @@ missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
|
||||
182 |+"1,2,3".split(",", maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
185 185 |
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
@@ -692,3 +693,43 @@ missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 |-"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
184 |+"1,2,3".split(maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
185 185 |
|
||||
186 186 |
|
||||
187 187 | ## Test unpacked list literal args (starred expressions)
|
||||
|
||||
missing_maxsplit_arg.py:189:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
187 | ## Test unpacked list literal args (starred expressions)
|
||||
188 | # Errors
|
||||
189 | "1,2,3".split(",", *[-1])[0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
190 |
|
||||
191 | ## Test unpacked list variable args
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
186 186 |
|
||||
187 187 | ## Test unpacked list literal args (starred expressions)
|
||||
188 188 | # Errors
|
||||
189 |-"1,2,3".split(",", *[-1])[0]
|
||||
189 |+"1,2,3".split(",", *[-1], maxsplit=1)[0]
|
||||
190 190 |
|
||||
191 191 | ## Test unpacked list variable args
|
||||
192 192 | # Errors
|
||||
|
||||
missing_maxsplit_arg.py:194:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
192 | # Errors
|
||||
193 | args_list = [-1]
|
||||
194 | "1,2,3".split(",", *args_list)[0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
191 191 | ## Test unpacked list variable args
|
||||
192 192 | # Errors
|
||||
193 193 | args_list = [-1]
|
||||
194 |-"1,2,3".split(",", *args_list)[0]
|
||||
194 |+"1,2,3".split(",", *args_list, maxsplit=1)[0]
|
||||
|
||||
@@ -11,7 +11,7 @@ invalid_characters_syntax_error.py:5:6: PLE2510 Invalid unescaped character back
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
invalid_characters_syntax_error.py:7:5: SyntaxError: missing closing quote in string literal
|
||||
invalid_characters_syntax_error.py:7:5: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
5 | b = '␈'
|
||||
6 | # Unterminated string
|
||||
@@ -21,7 +21,7 @@ invalid_characters_syntax_error.py:7:5: SyntaxError: missing closing quote in st
|
||||
9 | # Unterminated f-string
|
||||
|
|
||||
|
||||
invalid_characters_syntax_error.py:7:7: SyntaxError: Expected a statement
|
||||
invalid_characters_syntax_error.py:7:7: invalid-syntax: Expected a statement
|
||||
|
|
||||
5 | b = '␈'
|
||||
6 | # Unterminated string
|
||||
@@ -43,7 +43,7 @@ invalid_characters_syntax_error.py:8:6: PLE2510 Invalid unescaped character back
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
invalid_characters_syntax_error.py:10:7: SyntaxError: f-string: unterminated string
|
||||
invalid_characters_syntax_error.py:10:7: invalid-syntax: f-string: unterminated string
|
||||
|
|
||||
8 | b = '␈'
|
||||
9 | # Unterminated f-string
|
||||
@@ -53,7 +53,7 @@ invalid_characters_syntax_error.py:10:7: SyntaxError: f-string: unterminated str
|
||||
12 | # Implicitly concatenated
|
||||
|
|
||||
|
||||
invalid_characters_syntax_error.py:10:8: SyntaxError: Expected FStringEnd, found newline
|
||||
invalid_characters_syntax_error.py:10:8: invalid-syntax: Expected FStringEnd, found newline
|
||||
|
|
||||
8 | b = '␈'
|
||||
9 | # Unterminated f-string
|
||||
@@ -93,7 +93,7 @@ invalid_characters_syntax_error.py:13:11: PLE2510 Invalid unescaped character ba
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
invalid_characters_syntax_error.py:13:14: SyntaxError: missing closing quote in string literal
|
||||
invalid_characters_syntax_error.py:13:14: invalid-syntax: missing closing quote in string literal
|
||||
|
|
||||
11 | b = f'␈'
|
||||
12 | # Implicitly concatenated
|
||||
@@ -101,7 +101,7 @@ invalid_characters_syntax_error.py:13:14: SyntaxError: missing closing quote in
|
||||
| ^^
|
||||
|
|
||||
|
||||
invalid_characters_syntax_error.py:13:16: SyntaxError: Expected a statement
|
||||
invalid_characters_syntax_error.py:13:16: invalid-syntax: Expected a statement
|
||||
|
|
||||
11 | b = f'␈'
|
||||
12 | # Implicitly concatenated
|
||||
|
||||
@@ -96,7 +96,7 @@ enum EncodingArg<'a> {
|
||||
|
||||
/// Return the encoding argument to an `encode` call, if it can be determined to be a
|
||||
/// UTF-8-equivalent encoding.
|
||||
fn match_encoding_arg(arguments: &Arguments) -> Option<EncodingArg> {
|
||||
fn match_encoding_arg(arguments: &Arguments) -> Option<EncodingArg<'_>> {
|
||||
match (&*arguments.args, &*arguments.keywords) {
|
||||
// Ex `"".encode()`
|
||||
([], []) => return Some(EncodingArg::Empty),
|
||||
|
||||
@@ -343,7 +343,9 @@ enum ComprehensionTarget<'a> {
|
||||
}
|
||||
|
||||
/// Extract the target from the comprehension (e.g., `(x, y, z)` in `(x, y, z, ...) in iter`).
|
||||
fn match_comprehension_target(comprehension: &ast::Comprehension) -> Option<ComprehensionTarget> {
|
||||
fn match_comprehension_target(
|
||||
comprehension: &ast::Comprehension,
|
||||
) -> Option<ComprehensionTarget<'_>> {
|
||||
if comprehension.is_async || !comprehension.ifs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ pub(crate) fn single_item_membership_test(
|
||||
generate_comparison(
|
||||
left,
|
||||
&[membership_test.replacement_op()],
|
||||
&[item.clone()],
|
||||
std::slice::from_ref(item),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
|
||||
@@ -139,7 +139,7 @@ pub(crate) fn slice_to_remove_affix_stmt(checker: &Checker, if_stmt: &ast::StmtI
|
||||
/// where `func` is either `startswith` or `endswith`,
|
||||
/// this function collects `text`,`func`, `affix`, and the non-null
|
||||
/// bound of the slice. Otherwise, returns `None`.
|
||||
fn affix_removal_data_expr(if_expr: &ast::ExprIf) -> Option<RemoveAffixData> {
|
||||
fn affix_removal_data_expr(if_expr: &ast::ExprIf) -> Option<RemoveAffixData<'_>> {
|
||||
let ast::ExprIf {
|
||||
test,
|
||||
body,
|
||||
@@ -166,7 +166,7 @@ fn affix_removal_data_expr(if_expr: &ast::ExprIf) -> Option<RemoveAffixData> {
|
||||
/// where `func` is either `startswith` or `endswith`,
|
||||
/// this function collects `text`,`func`, `affix`, and the non-null
|
||||
/// bound of the slice. Otherwise, returns `None`.
|
||||
fn affix_removal_data_stmt(if_stmt: &ast::StmtIf) -> Option<RemoveAffixData> {
|
||||
fn affix_removal_data_stmt(if_stmt: &ast::StmtIf) -> Option<RemoveAffixData<'_>> {
|
||||
let ast::StmtIf {
|
||||
test,
|
||||
body,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
|
|
||||
1 | async def elements(n): yield n
|
||||
2 | [x async for x in elements(5)] # okay, async at top level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
<filename>:1:27: invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
|
|
||||
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:3:21: SyntaxError: attribute name `x` repeated in class pattern
|
||||
<filename>:3:21: invalid-syntax: attribute name `x` repeated in class pattern
|
||||
|
|
||||
2 | match x:
|
||||
3 | case Point(x=1, x=2):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:3:21: SyntaxError: mapping pattern checks duplicate key `'key'`
|
||||
<filename>:3:21: invalid-syntax: mapping pattern checks duplicate key `'key'`
|
||||
|
|
||||
2 | match x:
|
||||
3 | case {'key': 1, 'key': 2}:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user