Compare commits
517 Commits
dcreager/f
...
david/allo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b19177cd6d | ||
|
|
a1399656c9 | ||
|
|
6392dccd24 | ||
|
|
93ac0934dd | ||
|
|
aae4482c55 | ||
|
|
d02c9ada5d | ||
|
|
6c0a59ea78 | ||
|
|
0b181bc2ad | ||
|
|
0397682f1f | ||
|
|
bcefa459f4 | ||
|
|
91b7a570c2 | ||
|
|
98da200d45 | ||
|
|
029085fa72 | ||
|
|
6df10c638e | ||
|
|
bdf488462a | ||
|
|
01eeb2f0d6 | ||
|
|
cb04343b3b | ||
|
|
02394b8049 | ||
|
|
41463396cf | ||
|
|
da4be789ef | ||
|
|
02fd48132c | ||
|
|
d37592175f | ||
|
|
cb9e66927e | ||
|
|
76ab77fe01 | ||
|
|
7b253100f8 | ||
|
|
d098118e37 | ||
|
|
7917269d9a | ||
|
|
e8d4f6d891 | ||
|
|
60b486abce | ||
|
|
32403dfb28 | ||
|
|
76ab3425d3 | ||
|
|
90ca0a4c13 | ||
|
|
15dbfad265 | ||
|
|
4f8a005f8f | ||
|
|
3b56c7ca3d | ||
|
|
f9ca6eb63e | ||
|
|
8729cb208f | ||
|
|
a2c87c2bc1 | ||
|
|
b302d89da3 | ||
|
|
ce43dbab58 | ||
|
|
fb589730ef | ||
|
|
4fad15805b | ||
|
|
0ede831a3f | ||
|
|
d6009eb942 | ||
|
|
236633cd42 | ||
|
|
99cb89f90f | ||
|
|
ac5df56aa3 | ||
|
|
6985de4c40 | ||
|
|
55a410a885 | ||
|
|
97058e8093 | ||
|
|
569c94b71b | ||
|
|
59d80aff9f | ||
|
|
b913f568c4 | ||
|
|
4c889d5251 | ||
|
|
220137ca7b | ||
|
|
34337fb8ba | ||
|
|
38c332fe23 | ||
|
|
9f743d1b9f | ||
|
|
405544cc8f | ||
|
|
ab96adbcd1 | ||
|
|
a761b8cfa2 | ||
|
|
8c020cc2e9 | ||
|
|
c67aa0cce2 | ||
|
|
b00e390f3a | ||
|
|
1f9df0c8f0 | ||
|
|
9dd9227bca | ||
|
|
181a380ee0 | ||
|
|
12f5e99389 | ||
|
|
d9cd6399e6 | ||
|
|
c40a801002 | ||
|
|
04168cf1ce | ||
|
|
6d0703ae78 | ||
|
|
f7691a79a0 | ||
|
|
6e7340c68b | ||
|
|
5095248b7e | ||
|
|
b1e6c6edce | ||
|
|
8ee92c6c77 | ||
|
|
d6709abd94 | ||
|
|
660375d429 | ||
|
|
dd04ca7f58 | ||
|
|
b86960f18c | ||
|
|
2abcd86c57 | ||
|
|
c6e55f673c | ||
|
|
3d55a16c91 | ||
|
|
e21972a79b | ||
|
|
0adbb3d600 | ||
|
|
28fb802467 | ||
|
|
a1d007c37c | ||
|
|
1ba56b4bc6 | ||
|
|
e677cabd69 | ||
|
|
9910ec700c | ||
|
|
9ae698fe30 | ||
|
|
e67b35743a | ||
|
|
8644c9da43 | ||
|
|
196e4befba | ||
|
|
6e39250015 | ||
|
|
7dc4fefb47 | ||
|
|
e5435eb106 | ||
|
|
f53c580c53 | ||
|
|
2ceba6ae67 | ||
|
|
d3a7cb3fe4 | ||
|
|
69393b2e6e | ||
|
|
c066bf0127 | ||
|
|
a5ee1a3bb1 | ||
|
|
e2c5b83fe1 | ||
|
|
b35bf8ae07 | ||
|
|
279dac1c0e | ||
|
|
57617031de | ||
|
|
28b5a868d3 | ||
|
|
b6b7caa023 | ||
|
|
46be305ad2 | ||
|
|
c3a4992ae9 | ||
|
|
9aa6330bb1 | ||
|
|
b600ff106a | ||
|
|
466021d5e1 | ||
|
|
33e14c5963 | ||
|
|
6800a9f6f3 | ||
|
|
68559fc17d | ||
|
|
2a217e80ca | ||
|
|
030a16cb5f | ||
|
|
0590b38214 | ||
|
|
8104b1e83b | ||
|
|
cf70c7863c | ||
|
|
faf54c0181 | ||
|
|
451c5db7a3 | ||
|
|
bd5b7f415f | ||
|
|
0230cbac2c | ||
|
|
2e94d37275 | ||
|
|
1e4377c9c6 | ||
|
|
1b4f7de840 | ||
|
|
9b52ae8991 | ||
|
|
97d7b46936 | ||
|
|
1eab59e681 | ||
|
|
e7f97a3e4b | ||
|
|
d17557f0ae | ||
|
|
8cbd433a31 | ||
|
|
65e48cb439 | ||
|
|
301d9985d8 | ||
|
|
cfbb914100 | ||
|
|
fe653de3dd | ||
|
|
a9f7521944 | ||
|
|
f8890b70c3 | ||
|
|
142c1bc760 | ||
|
|
c0f22928bd | ||
|
|
5bf5f3682a | ||
|
|
5913997c72 | ||
|
|
00f672a83b | ||
|
|
68b0386007 | ||
|
|
0ae07cdd1f | ||
|
|
0fb94c052e | ||
|
|
55df9271ba | ||
|
|
f301931159 | ||
|
|
7e9b0df18a | ||
|
|
41fa082414 | ||
|
|
c7b6108cb8 | ||
|
|
a97e72fb5e | ||
|
|
0d6fafd0f9 | ||
|
|
2eb2d5359b | ||
|
|
f549dfe39d | ||
|
|
6b64630635 | ||
|
|
d545b5bfd2 | ||
|
|
b2d9f59937 | ||
|
|
d7ef01401c | ||
|
|
c9031ce59f | ||
|
|
138ab91def | ||
|
|
550b8be552 | ||
|
|
bdccb37b4a | ||
|
|
3ccc0edfe4 | ||
|
|
6b3ff6f5b8 | ||
|
|
6f8f7506b4 | ||
|
|
797eb70904 | ||
|
|
be6ec613db | ||
|
|
fcd858e0c8 | ||
|
|
d944a1397e | ||
|
|
d3f3d92df3 | ||
|
|
38c00dfad5 | ||
|
|
d6280c5aea | ||
|
|
a34240a3f0 | ||
|
|
b86c7bbf7c | ||
|
|
d7c54ba8c4 | ||
|
|
c38d6e8045 | ||
|
|
2bfd7b1816 | ||
|
|
c1cfb43bf0 | ||
|
|
99555b775c | ||
|
|
b398b83631 | ||
|
|
bc7b30364d | ||
|
|
5792ed15da | ||
|
|
8845a13efb | ||
|
|
669855d2b5 | ||
|
|
ff7ebecf89 | ||
|
|
7e8ba2b68e | ||
|
|
0bb8cbdf07 | ||
|
|
2923c55698 | ||
|
|
316e406ca4 | ||
|
|
5ecd560c6f | ||
|
|
b765dc48e9 | ||
|
|
cd1d906ffa | ||
|
|
235b74a310 | ||
|
|
40fd52dde0 | ||
|
|
fd1eb3d801 | ||
|
|
882a1a702e | ||
|
|
b4a1ebdfe3 | ||
|
|
7a48477c67 | ||
|
|
346e82b572 | ||
|
|
5ea3a52c8a | ||
|
|
90272ad85a | ||
|
|
e9da1750a1 | ||
|
|
25e13debc0 | ||
|
|
249a852a6e | ||
|
|
861ef2504e | ||
|
|
b71ef8a26e | ||
|
|
50c780fc8b | ||
|
|
244ea27d5f | ||
|
|
2c4cbb6e29 | ||
|
|
d1bb10a66b | ||
|
|
2370297cde | ||
|
|
a137cb18d4 | ||
|
|
03a4d56624 | ||
|
|
642eac452d | ||
|
|
c1b875799b | ||
|
|
6cd8a49638 | ||
|
|
12ce445ff7 | ||
|
|
f46ed8d410 | ||
|
|
6c177e2bbe | ||
|
|
3d2485eb1b | ||
|
|
f78367979e | ||
|
|
b705664d49 | ||
|
|
f51f1f7153 | ||
|
|
9b694ada82 | ||
|
|
4d81a41107 | ||
|
|
981bd70d39 | ||
|
|
0763331f7f | ||
|
|
da8540862d | ||
|
|
6a5533c44c | ||
|
|
d608eae126 | ||
|
|
067a8ac574 | ||
|
|
5eb215e8e5 | ||
|
|
91aa853b9c | ||
|
|
57bf7dfbd9 | ||
|
|
67cd94ed64 | ||
|
|
aac862822f | ||
|
|
3755ac9fac | ||
|
|
4f890b2867 | ||
|
|
d566636ca5 | ||
|
|
51cef5a72b | ||
|
|
2cf5cba7ff | ||
|
|
ce0800fccf | ||
|
|
d03a7069ad | ||
|
|
f5096f2050 | ||
|
|
895b6161a6 | ||
|
|
74fe7982ba | ||
|
|
51386b3c7a | ||
|
|
51e2effd2d | ||
|
|
82d31a6014 | ||
|
|
78054824c0 | ||
|
|
c6f4929cdc | ||
|
|
2ec0d7e072 | ||
|
|
ad658f4d68 | ||
|
|
3dedd70a92 | ||
|
|
fab862c8cd | ||
|
|
0d9b6a0975 | ||
|
|
c5e299e796 | ||
|
|
c504001b32 | ||
|
|
04457f99b6 | ||
|
|
a33d0d4bf4 | ||
|
|
443f62e98d | ||
|
|
a2e9a7732a | ||
|
|
b2de749c32 | ||
|
|
9085f18353 | ||
|
|
8152ba7cb7 | ||
|
|
3d01d3be3e | ||
|
|
4510a236d3 | ||
|
|
76b6d53d8b | ||
|
|
f82b72882b | ||
|
|
d07eefc408 | ||
|
|
f7237e3b69 | ||
|
|
2f9992b6ef | ||
|
|
457ec4dddd | ||
|
|
aa0614509b | ||
|
|
9000eb3bfd | ||
|
|
7f50b503cf | ||
|
|
24d3fc27fb | ||
|
|
6f821ac846 | ||
|
|
d410d12bc5 | ||
|
|
89424cce5f | ||
|
|
fd76d70a31 | ||
|
|
a4c8e43c5f | ||
|
|
ada4c4cb1f | ||
|
|
bb6c7cad07 | ||
|
|
47e3aa40b3 | ||
|
|
9a6633da0b | ||
|
|
de78da5ee6 | ||
|
|
20d64b9c85 | ||
|
|
4850c187ea | ||
|
|
3f32446e16 | ||
|
|
784daae497 | ||
|
|
178c882740 | ||
|
|
101e1a5ddd | ||
|
|
965a4dd731 | ||
|
|
5e2c818417 | ||
|
|
90c12f4177 | ||
|
|
6e9fb9af38 | ||
|
|
a507c1b8b3 | ||
|
|
5a91badb8b | ||
|
|
1945bfdb84 | ||
|
|
a95c73d5d0 | ||
|
|
78b4c3ccf1 | ||
|
|
2485afe640 | ||
|
|
b8ed729f59 | ||
|
|
108c470348 | ||
|
|
87c64c9eab | ||
|
|
a10606dda2 | ||
|
|
d1c6dd9ac1 | ||
|
|
073b993ab0 | ||
|
|
6a36cd6f02 | ||
|
|
3b15af6d4f | ||
|
|
e95130ad80 | ||
|
|
68e32c103f | ||
|
|
fe4051b2e6 | ||
|
|
fa628018b2 | ||
|
|
8535af8516 | ||
|
|
b51c4f82ea | ||
|
|
e6a798b962 | ||
|
|
52b0470870 | ||
|
|
c4a08782cc | ||
|
|
91481a8be7 | ||
|
|
097af060c9 | ||
|
|
b7d0b3f9e5 | ||
|
|
084352f72c | ||
|
|
78d4356301 | ||
|
|
96697c98f3 | ||
|
|
f7cae4ffb5 | ||
|
|
675a5af89a | ||
|
|
ea3f4ac059 | ||
|
|
6d2c10cca2 | ||
|
|
3cf44e401a | ||
|
|
17050e2ec5 | ||
|
|
a6dc04f96e | ||
|
|
e515899141 | ||
|
|
0c80c56afc | ||
|
|
b7ce694162 | ||
|
|
163d526407 | ||
|
|
75effb8ed7 | ||
|
|
3353d07938 | ||
|
|
41f3f21629 | ||
|
|
76ec64d535 | ||
|
|
b7e69ecbfc | ||
|
|
9c57862262 | ||
|
|
67ef370733 | ||
|
|
e17e1e860b | ||
|
|
03d8679adf | ||
|
|
d33a503686 | ||
|
|
650cbdd296 | ||
|
|
d2a238dfad | ||
|
|
6e765b4527 | ||
|
|
c5e41c278c | ||
|
|
0eeb02c0c1 | ||
|
|
f31b1c695c | ||
|
|
5679bf00bc | ||
|
|
a7c358ab5c | ||
|
|
b6de01b9a5 | ||
|
|
18bac94226 | ||
|
|
7568eeb7a5 | ||
|
|
0e85cbdd91 | ||
|
|
7825975972 | ||
|
|
f584b66824 | ||
|
|
ad1a8da4d1 | ||
|
|
0861ecfa55 | ||
|
|
d1f359afbb | ||
|
|
b84b58760e | ||
|
|
d94be0e780 | ||
|
|
8a6787b39e | ||
|
|
4a621c2c12 | ||
|
|
2bb99df394 | ||
|
|
f11d9cb509 | ||
|
|
549ab74bd6 | ||
|
|
81fc7d7d3a | ||
|
|
8c68d30c3a | ||
|
|
93d6a3567b | ||
|
|
1d788981cd | ||
|
|
7d46579808 | ||
|
|
c9a6b1a9d0 | ||
|
|
9b9d16c3ba | ||
|
|
79f8473e51 | ||
|
|
ca4fdf452d | ||
|
|
3c460a7b9a | ||
|
|
31e6576971 | ||
|
|
c953e7d143 | ||
|
|
5096824793 | ||
|
|
ae7691b026 | ||
|
|
504fa20057 | ||
|
|
f0868ac0c9 | ||
|
|
01a31c08f5 | ||
|
|
405878a128 | ||
|
|
80103a179d | ||
|
|
9a8f3cf247 | ||
|
|
07718f4788 | ||
|
|
1e8881f9af | ||
|
|
152a0b6585 | ||
|
|
1ad5015e19 | ||
|
|
92f95ff494 | ||
|
|
ceb2bf1168 | ||
|
|
f521358033 | ||
|
|
74081032d9 | ||
|
|
dbc137c951 | ||
|
|
826b2c9ff3 | ||
|
|
a3e55cfd8f | ||
|
|
d2246278e6 | ||
|
|
6bd1863bf0 | ||
|
|
97dc58fc77 | ||
|
|
53a9448fb5 | ||
|
|
516291b693 | ||
|
|
b09f00a4ef | ||
|
|
03065c245c | ||
|
|
b45598389d | ||
|
|
4729ff2bc8 | ||
|
|
1bdb22c139 | ||
|
|
1c65e0ad25 | ||
|
|
4443f6653c | ||
|
|
b0d475f353 | ||
|
|
b578a828ef | ||
|
|
64ba39a385 | ||
|
|
a4e225ee8a | ||
|
|
45d0634b01 | ||
|
|
4bcf1778fa | ||
|
|
6044f04137 | ||
|
|
2e95475f57 | ||
|
|
cfa1505068 | ||
|
|
0251679f87 | ||
|
|
6ab32a7746 | ||
|
|
bc0a5aa409 | ||
|
|
aba21a5d47 | ||
|
|
b6281a8805 | ||
|
|
049280a3bc | ||
|
|
fa88989ef0 | ||
|
|
4c3f389598 | ||
|
|
6d3b1d13d6 | ||
|
|
3f84e75e20 | ||
|
|
afc18ff1a1 | ||
|
|
f1a539dac6 | ||
|
|
ef0343189c | ||
|
|
4eecc40110 | ||
|
|
cf59cee928 | ||
|
|
538393d1f3 | ||
|
|
92ecfc908b | ||
|
|
f7b48510b5 | ||
|
|
9937064761 | ||
|
|
8d2c79276d | ||
|
|
0f47810768 | ||
|
|
eb1d2518c1 | ||
|
|
a45a0a92bd | ||
|
|
43bd043755 | ||
|
|
9a54ee3a1c | ||
|
|
25c3be51d2 | ||
|
|
e71f3ed2c5 | ||
|
|
ac6219ec38 | ||
|
|
e93fa7062c | ||
|
|
21fd28d713 | ||
|
|
a01f25107a | ||
|
|
48a85c4ed4 | ||
|
|
1796ca97d5 | ||
|
|
e897f37911 | ||
|
|
00e73dc331 | ||
|
|
7b6222700b | ||
|
|
bfc1650198 | ||
|
|
d5410ef9fe | ||
|
|
9db63fc58c | ||
|
|
61e73481fe | ||
|
|
e170fe493d | ||
|
|
e91e2f49db | ||
|
|
b537552927 | ||
|
|
5a719f2d60 | ||
|
|
e7f38fe74b | ||
|
|
624f5c6c22 | ||
|
|
8abf93f5fb | ||
|
|
5407249467 | ||
|
|
0a1f9d090e | ||
|
|
f9c7908bb7 | ||
|
|
99fa850e53 | ||
|
|
a241321735 | ||
|
|
b1b8ca3bcd | ||
|
|
3fae176345 | ||
|
|
f36262d970 | ||
|
|
e45f23b0ec | ||
|
|
aa46047649 | ||
|
|
f9da115fdc | ||
|
|
3872d57463 | ||
|
|
27ada26ddb | ||
|
|
810478f68b | ||
|
|
17f799424a | ||
|
|
c12640fea8 | ||
|
|
3796b13ea2 | ||
|
|
ad5a659f29 | ||
|
|
27a377f077 | ||
|
|
b8b624d890 | ||
|
|
6dc2d29966 | ||
|
|
890ba725d9 | ||
|
|
298f43f34e | ||
|
|
3b300559ab | ||
|
|
14f71ceb83 | ||
|
|
4775719abf | ||
|
|
6bdffc3cbf | ||
|
|
775815ef22 | ||
|
|
0299a52fb1 | ||
|
|
83d5ad8983 | ||
|
|
ae6fde152c | ||
|
|
d2b20f7367 | ||
|
|
38a3b056e3 | ||
|
|
37a0836bd2 | ||
|
|
f83295fe51 | ||
|
|
c4581788b2 | ||
|
|
2894aaa943 | ||
|
|
ed4866a00b | ||
|
|
9b5fe51b32 | ||
|
|
53ffe7143f | ||
|
|
21561000b1 | ||
|
|
9c0772d8f0 |
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -12,9 +12,16 @@ crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_
|
|||||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
|
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
|
||||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr
|
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr
|
||||||
|
|
||||||
|
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py text eol=cr
|
||||||
|
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py text eol=lf
|
||||||
|
|
||||||
|
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
||||||
|
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||||
|
|
||||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||||
|
|
||||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||||
|
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||||
crates/ruff_python_ast/src/generated.rs -diff linguist-generated=true text=auto eol=lf
|
crates/ruff_python_ast/src/generated.rs -diff linguist-generated=true text=auto eol=lf
|
||||||
crates/ruff_python_formatter/src/generated.rs -diff linguist-generated=true text=auto eol=lf
|
crates/ruff_python_formatter/src/generated.rs -diff linguist-generated=true text=auto eol=lf
|
||||||
*.md.snap linguist-language=Markdown
|
*.md.snap linguist-language=Markdown
|
||||||
|
|||||||
10
.github/CODEOWNERS
vendored
10
.github/CODEOWNERS
vendored
@@ -14,11 +14,11 @@
|
|||||||
# flake8-pyi
|
# flake8-pyi
|
||||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||||
|
|
||||||
# Script for fuzzing the parser/red-knot etc.
|
# Script for fuzzing the parser/ty etc.
|
||||||
/python/py-fuzzer/ @AlexWaygood
|
/python/py-fuzzer/ @AlexWaygood
|
||||||
|
|
||||||
# red-knot
|
# ty
|
||||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
/crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||||
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
/scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||||
/crates/red_knot_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
|
/crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Report an issue with ty
|
||||||
|
url: https://github.com/astral-sh/ty/issues/new/choose
|
||||||
|
about: Please report issues for our type checker ty in the ty repository.
|
||||||
- name: Documentation
|
- name: Documentation
|
||||||
url: https://docs.astral.sh/ruff
|
url: https://docs.astral.sh/ruff
|
||||||
about: Please consult the documentation before creating an issue.
|
about: Please consult the documentation before creating an issue.
|
||||||
|
|||||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,8 +1,9 @@
|
|||||||
<!--
|
<!--
|
||||||
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
|
Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following:
|
||||||
|
|
||||||
- Does this pull request include a summary of the change? (See below.)
|
- Does this pull request include a summary of the change? (See below.)
|
||||||
- Does this pull request include a descriptive title?
|
- Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull
|
||||||
|
requests.)
|
||||||
- Does this pull request include references to any relevant issues?
|
- Does this pull request include references to any relevant issues?
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
1
.github/actionlint.yaml
vendored
1
.github/actionlint.yaml
vendored
@@ -6,5 +6,6 @@ self-hosted-runner:
|
|||||||
labels:
|
labels:
|
||||||
- depot-ubuntu-latest-8
|
- depot-ubuntu-latest-8
|
||||||
- depot-ubuntu-22.04-16
|
- depot-ubuntu-22.04-16
|
||||||
|
- depot-ubuntu-22.04-32
|
||||||
- github-windows-2025-x86_64-8
|
- github-windows-2025-x86_64-8
|
||||||
- github-windows-2025-x86_64-16
|
- github-windows-2025-x86_64-16
|
||||||
|
|||||||
8
.github/mypy-primer-ty.toml
vendored
Normal file
8
.github/mypy-primer-ty.toml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#:schema ../ty.schema.json
|
||||||
|
# Configuration overrides for the mypy primer run
|
||||||
|
|
||||||
|
# Enable off-by-default rules.
|
||||||
|
[rules]
|
||||||
|
possibly-unresolved-reference = "warn"
|
||||||
|
unused-ignore-comment = "warn"
|
||||||
|
division-by-zero = "warn"
|
||||||
20
.github/workflows/build-binaries.yml
vendored
20
.github/workflows/build-binaries.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: arm64
|
architecture: arm64
|
||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: ${{ matrix.platform.arch }}
|
architecture: ${{ matrix.platform.arch }}
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -298,7 +298,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
@@ -310,7 +310,7 @@ jobs:
|
|||||||
manylinux: auto
|
manylinux: auto
|
||||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||||
args: --release --locked --out dist
|
args: --release --locked --out dist
|
||||||
- uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2
|
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
|
||||||
if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}}
|
if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}}
|
||||||
name: Test wheel
|
name: Test wheel
|
||||||
with:
|
with:
|
||||||
@@ -363,7 +363,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -429,7 +429,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
@@ -441,7 +441,7 @@ jobs:
|
|||||||
manylinux: musllinux_1_2
|
manylinux: musllinux_1_2
|
||||||
args: --release --locked --out dist
|
args: --release --locked --out dist
|
||||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||||
- uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2
|
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
|
||||||
name: Test wheel
|
name: Test wheel
|
||||||
with:
|
with:
|
||||||
arch: ${{ matrix.platform.arch }}
|
arch: ${{ matrix.platform.arch }}
|
||||||
|
|||||||
32
.github/workflows/build-docker.yml
vendored
32
.github/workflows/build-docker.yml
vendored
@@ -38,9 +38,9 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
|
|
||||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
with:
|
with:
|
||||||
images: ${{ env.RUFF_BASE_IMG }}
|
images: ${{ env.RUFF_BASE_IMG }}
|
||||||
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
|
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||||
- name: Build and push by digest
|
- name: Build and push by digest
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
@@ -113,17 +113,17 @@ jobs:
|
|||||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
with:
|
with:
|
||||||
images: ${{ env.RUFF_BASE_IMG }}
|
images: ${{ env.RUFF_BASE_IMG }}
|
||||||
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
|
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||||
|
|
||||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -167,9 +167,9 @@ jobs:
|
|||||||
- debian:bookworm-slim,bookworm-slim,debian-slim
|
- debian:bookworm-slim,bookworm-slim,debian-slim
|
||||||
- buildpack-deps:bookworm,bookworm,debian
|
- buildpack-deps:bookworm,bookworm,debian
|
||||||
steps:
|
steps:
|
||||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
|
|
||||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -219,7 +219,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
# ghcr.io prefers index level annotations
|
# ghcr.io prefers index level annotations
|
||||||
env:
|
env:
|
||||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||||
@@ -231,7 +231,7 @@ jobs:
|
|||||||
${{ env.TAG_PATTERNS }}
|
${{ env.TAG_PATTERNS }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -256,17 +256,17 @@ jobs:
|
|||||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
env:
|
env:
|
||||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||||
with:
|
with:
|
||||||
@@ -276,7 +276,7 @@ jobs:
|
|||||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||||
|
|
||||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
131
.github/workflows/ci.yaml
vendored
131
.github/workflows/ci.yaml
vendored
@@ -36,8 +36,8 @@ jobs:
|
|||||||
code: ${{ steps.check_code.outputs.changed }}
|
code: ${{ steps.check_code.outputs.changed }}
|
||||||
# Flag that is raised when any code that affects the fuzzer is changed
|
# Flag that is raised when any code that affects the fuzzer is changed
|
||||||
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
|
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
|
||||||
# Flag that is set to "true" when code related to red-knot changes.
|
# Flag that is set to "true" when code related to ty changes.
|
||||||
red_knot: ${{ steps.check_red_knot.outputs.changed }}
|
ty: ${{ steps.check_ty.outputs.changed }}
|
||||||
|
|
||||||
# Flag that is set to "true" when code related to the playground changes.
|
# Flag that is set to "true" when code related to the playground changes.
|
||||||
playground: ${{ steps.check_playground.outputs.changed }}
|
playground: ${{ steps.check_playground.outputs.changed }}
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
||||||
':Cargo.lock' \
|
':Cargo.lock' \
|
||||||
':crates/**' \
|
':crates/**' \
|
||||||
':!crates/red_knot*/**' \
|
':!crates/ty*/**' \
|
||||||
':!crates/ruff_python_formatter/**' \
|
':!crates/ruff_python_formatter/**' \
|
||||||
':!crates/ruff_formatter/**' \
|
':!crates/ruff_formatter/**' \
|
||||||
':!crates/ruff_dev/**' \
|
':!crates/ruff_dev/**' \
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**' \
|
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**' \
|
||||||
':!**/*.md' \
|
':!**/*.md' \
|
||||||
':crates/red_knot_python_semantic/resources/mdtest/**/*.md' \
|
':crates/ty_python_semantic/resources/mdtest/**/*.md' \
|
||||||
':!docs/**' \
|
':!docs/**' \
|
||||||
':!assets/**' \
|
':!assets/**' \
|
||||||
':.github/workflows/ci.yaml' \
|
':.github/workflows/ci.yaml' \
|
||||||
@@ -168,15 +168,15 @@ jobs:
|
|||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Check if the red-knot code changed
|
- name: Check if the ty code changed
|
||||||
id: check_red_knot
|
id: check_ty
|
||||||
env:
|
env:
|
||||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||||
run: |
|
run: |
|
||||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||||
':Cargo.toml' \
|
':Cargo.toml' \
|
||||||
':Cargo.lock' \
|
':Cargo.lock' \
|
||||||
':crates/red_knot*/**' \
|
':crates/ty*/**' \
|
||||||
':crates/ruff_db/**' \
|
':crates/ruff_db/**' \
|
||||||
':crates/ruff_annotate_snippets/**' \
|
':crates/ruff_annotate_snippets/**' \
|
||||||
':crates/ruff_python_ast/**' \
|
':crates/ruff_python_ast/**' \
|
||||||
@@ -221,7 +221,7 @@ jobs:
|
|||||||
- name: "Clippy"
|
- name: "Clippy"
|
||||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||||
- name: "Clippy (wasm)"
|
- name: "Clippy (wasm)"
|
||||||
run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
run: cargo clippy -p ruff_wasm -p ty_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||||
|
|
||||||
cargo-test-linux:
|
cargo-test-linux:
|
||||||
name: "cargo test (linux)"
|
name: "cargo test (linux)"
|
||||||
@@ -239,21 +239,21 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: Red-knot mdtests (GitHub annotations)
|
- name: ty mdtests (GitHub annotations)
|
||||||
if: ${{ needs.determine_changes.outputs.red_knot == 'true' }}
|
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
|
||||||
env:
|
env:
|
||||||
NO_COLOR: 1
|
NO_COLOR: 1
|
||||||
MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
|
MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
|
||||||
# Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
|
# Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
|
||||||
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
||||||
run: cargo test -p red_knot_python_semantic --test mdtest || true
|
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
@@ -268,7 +268,7 @@ jobs:
|
|||||||
# sync, not just public items. Eventually we should do this for all
|
# sync, not just public items. Eventually we should do this for all
|
||||||
# crates; for now add crates here as they are warning-clean to prevent
|
# crates; for now add crates here as they are warning-clean to prevent
|
||||||
# regression.
|
# regression.
|
||||||
- run: cargo doc --no-deps -p red_knot_python_semantic -p red_knot -p red_knot_test -p ruff_db --document-private-items
|
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
|
||||||
env:
|
env:
|
||||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||||
RUSTDOCFLAGS: "-D warnings"
|
RUSTDOCFLAGS: "-D warnings"
|
||||||
@@ -276,6 +276,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
path: target/debug/ruff
|
path: target/debug/ruff
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: ty
|
||||||
|
path: target/debug/ty
|
||||||
|
|
||||||
cargo-test-linux-release:
|
cargo-test-linux-release:
|
||||||
name: "cargo test (linux, release)"
|
name: "cargo test (linux, release)"
|
||||||
@@ -293,11 +297,11 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
@@ -320,7 +324,7 @@ jobs:
|
|||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
@@ -346,7 +350,7 @@ jobs:
|
|||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
@@ -358,9 +362,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd crates/ruff_wasm
|
cd crates/ruff_wasm
|
||||||
wasm-pack test --node
|
wasm-pack test --node
|
||||||
- name: "Test red_knot_wasm"
|
- name: "Test ty_wasm"
|
||||||
run: |
|
run: |
|
||||||
cd crates/red_knot_wasm
|
cd crates/ty_wasm
|
||||||
wasm-pack test --node
|
wasm-pack test --node
|
||||||
|
|
||||||
cargo-build-release:
|
cargo-build-release:
|
||||||
@@ -403,11 +407,11 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
@@ -433,7 +437,7 @@ jobs:
|
|||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install cargo-binstall"
|
- name: "Install cargo-binstall"
|
||||||
uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
uses: cargo-bins/cargo-binstall@5cbf019d8cb9b9d5b086218c41458ea35d817691 # v1.12.5
|
||||||
with:
|
with:
|
||||||
tool: cargo-fuzz@0.11.2
|
tool: cargo-fuzz@0.11.2
|
||||||
- name: "Install cargo-fuzz"
|
- name: "Install cargo-fuzz"
|
||||||
@@ -455,8 +459,8 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
name: Download Ruff binary to test
|
name: Download Ruff binary to test
|
||||||
id: download-cached-binary
|
id: download-cached-binary
|
||||||
with:
|
with:
|
||||||
@@ -500,12 +504,10 @@ jobs:
|
|||||||
# Verify that adding a plugin or rule produces clean code.
|
# Verify that adding a plugin or rule produces clean code.
|
||||||
- run: ./scripts/add_rule.py --name DoTheThing --prefix F --code 999 --linter pyflakes
|
- run: ./scripts/add_rule.py --name DoTheThing --prefix F --code 999 --linter pyflakes
|
||||||
- run: cargo check
|
- run: cargo check
|
||||||
- run: cargo fmt --all --check
|
|
||||||
- run: |
|
- run: |
|
||||||
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
|
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
|
||||||
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
|
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
|
||||||
- run: cargo check
|
- run: cargo check
|
||||||
- run: cargo fmt --all --check
|
|
||||||
|
|
||||||
ecosystem:
|
ecosystem:
|
||||||
name: "ecosystem"
|
name: "ecosystem"
|
||||||
@@ -521,11 +523,11 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
name: Download comparison Ruff binary
|
name: Download comparison Ruff binary
|
||||||
id: ruff-target
|
id: ruff-target
|
||||||
with:
|
with:
|
||||||
@@ -632,6 +634,53 @@ jobs:
|
|||||||
name: ecosystem-result
|
name: ecosystem-result
|
||||||
path: ecosystem-result
|
path: ecosystem-result
|
||||||
|
|
||||||
|
fuzz-ty:
|
||||||
|
name: "Fuzz for new ty panics"
|
||||||
|
runs-on: depot-ubuntu-22.04-16
|
||||||
|
needs:
|
||||||
|
- cargo-test-linux
|
||||||
|
- determine_changes
|
||||||
|
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||||
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.ty == 'true' }}
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
name: Download new ty binary
|
||||||
|
id: ty-new
|
||||||
|
with:
|
||||||
|
name: ty
|
||||||
|
path: target/debug
|
||||||
|
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||||
|
name: Download baseline ty binary
|
||||||
|
with:
|
||||||
|
name: ty
|
||||||
|
branch: ${{ github.event.pull_request.base.ref }}
|
||||||
|
workflow: "ci.yaml"
|
||||||
|
check_artifacts: true
|
||||||
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
|
- name: Fuzz
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
NEW_TY: ${{ steps.ty-new.outputs.download-path }}
|
||||||
|
run: |
|
||||||
|
# Make executable, since artifact download doesn't preserve this
|
||||||
|
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
|
||||||
|
|
||||||
|
(
|
||||||
|
uvx \
|
||||||
|
--python="${PYTHON_VERSION}" \
|
||||||
|
--from=./python/py-fuzzer \
|
||||||
|
fuzz \
|
||||||
|
--test-executable="${NEW_TY}/ty" \
|
||||||
|
--baseline-executable="${PWD}/ty" \
|
||||||
|
--only-new-bugs \
|
||||||
|
--bin=ty \
|
||||||
|
0-500
|
||||||
|
)
|
||||||
|
|
||||||
cargo-shear:
|
cargo-shear:
|
||||||
name: "cargo shear"
|
name: "cargo shear"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -641,7 +690,7 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
- uses: cargo-bins/cargo-binstall@5cbf019d8cb9b9d5b086218c41458ea35d817691 # v1.12.5
|
||||||
- run: cargo binstall --no-confirm cargo-shear
|
- run: cargo binstall --no-confirm cargo-shear
|
||||||
- run: cargo shear
|
- run: cargo shear
|
||||||
|
|
||||||
@@ -654,7 +703,7 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -681,7 +730,11 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
|
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
- name: "Cache pre-commit"
|
- name: "Cache pre-commit"
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
with:
|
with:
|
||||||
@@ -708,7 +761,7 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||||
@@ -720,7 +773,7 @@ jobs:
|
|||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
- name: "Install Insiders dependencies"
|
- name: "Install Insiders dependencies"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||||
@@ -769,7 +822,7 @@ jobs:
|
|||||||
- determine_changes
|
- determine_changes
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
|
- uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -779,12 +832,12 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: "astral-sh/ruff-lsp"
|
repository: "astral-sh/ruff-lsp"
|
||||||
|
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
# installation fails on 3.13 and newer
|
# installation fails on 3.13 and newer
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
name: Download development ruff binary
|
name: Download development ruff binary
|
||||||
id: ruff-target
|
id: ruff-target
|
||||||
with:
|
with:
|
||||||
@@ -821,7 +874,7 @@ jobs:
|
|||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
@@ -857,7 +910,7 @@ jobs:
|
|||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
|
|||||||
72
.github/workflows/daily_property_tests.yaml
vendored
72
.github/workflows/daily_property_tests.yaml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: Daily property test run
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 12 * * *"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/daily_property_tests.yaml"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
CARGO_NET_RETRY: 10
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
RUSTUP_MAX_RETRIES: 10
|
|
||||||
FORCE_COLOR: 1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
property_tests:
|
|
||||||
name: Property tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 20
|
|
||||||
# Don't run the cron job on forks:
|
|
||||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: "Install Rust toolchain"
|
|
||||||
run: rustup show
|
|
||||||
- name: "Install mold"
|
|
||||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
|
||||||
- name: Build Red Knot
|
|
||||||
# A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
|
|
||||||
# mode (1.5 min vs 14 min), so the overall time is shorter with a release build.
|
|
||||||
run: cargo build --locked --release --package red_knot_python_semantic --tests
|
|
||||||
- name: Run property tests
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
export QUICKCHECK_TESTS=100000
|
|
||||||
for _ in {1..5}; do
|
|
||||||
cargo test --locked --release --package red_knot_python_semantic -- --ignored list::property_tests
|
|
||||||
cargo test --locked --release --package red_knot_python_semantic -- --ignored types::property_tests::stable
|
|
||||||
done
|
|
||||||
|
|
||||||
create-issue-on-failure:
|
|
||||||
name: Create an issue if the daily property test run surfaced any bugs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: property_tests
|
|
||||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.property_tests.result == 'failure' }}
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
await github.rest.issues.create({
|
|
||||||
owner: "astral-sh",
|
|
||||||
repo: "ruff",
|
|
||||||
title: `Daily property test run failed on ${new Date().toDateString()}`,
|
|
||||||
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
||||||
labels: ["bug", "red-knot", "testing"],
|
|
||||||
})
|
|
||||||
25
.github/workflows/mypy_primer.yaml
vendored
25
.github/workflows/mypy_primer.yaml
vendored
@@ -5,12 +5,13 @@ permissions: {}
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "crates/red_knot*/**"
|
- "crates/ty*/**"
|
||||||
- "crates/ruff_db"
|
- "crates/ruff_db"
|
||||||
- "crates/ruff_python_ast"
|
- "crates/ruff_python_ast"
|
||||||
- "crates/ruff_python_parser"
|
- "crates/ruff_python_parser"
|
||||||
- ".github/workflows/mypy_primer.yaml"
|
- ".github/workflows/mypy_primer.yaml"
|
||||||
- ".github/workflows/mypy_primer_comment.yaml"
|
- ".github/workflows/mypy_primer_comment.yaml"
|
||||||
|
- "Cargo.lock"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||||
@@ -21,11 +22,12 @@ env:
|
|||||||
CARGO_NET_RETRY: 10
|
CARGO_NET_RETRY: 10
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUSTUP_MAX_RETRIES: 10
|
RUSTUP_MAX_RETRIES: 10
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
mypy_primer:
|
mypy_primer:
|
||||||
name: Run mypy_primer
|
name: Run mypy_primer
|
||||||
runs-on: depot-ubuntu-22.04-16
|
runs-on: depot-ubuntu-22.04-32
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@@ -35,24 +37,25 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: Install mypy_primer
|
|
||||||
run: |
|
|
||||||
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support-v5"
|
|
||||||
|
|
||||||
- name: Run mypy_primer
|
- name: Run mypy_primer
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd ruff
|
cd ruff
|
||||||
|
|
||||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt)"
|
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
|
||||||
|
mkdir -p ~/.config/ty
|
||||||
|
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||||
|
|
||||||
|
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
|
||||||
|
|
||||||
echo "new commit"
|
echo "new commit"
|
||||||
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||||
@@ -66,9 +69,11 @@ jobs:
|
|||||||
|
|
||||||
echo "Project selector: $PRIMER_SELECTOR"
|
echo "Project selector: $PRIMER_SELECTOR"
|
||||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||||
uvx mypy_primer \
|
uvx \
|
||||||
|
--from="git+https://github.com/hauntsaninja/mypy_primer@01a7ca325f674433c58e02416a867178d1571128" \
|
||||||
|
mypy_primer \
|
||||||
--repo ruff \
|
--repo ruff \
|
||||||
--type-checker knot \
|
--type-checker ty \
|
||||||
--old base_commit \
|
--old base_commit \
|
||||||
--new "$GITHUB_SHA" \
|
--new "$GITHUB_SHA" \
|
||||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||||
|
|||||||
2
.github/workflows/mypy_primer_comment.yaml
vendored
2
.github/workflows/mypy_primer_comment.yaml
vendored
@@ -79,7 +79,7 @@ jobs:
|
|||||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Find existing comment
|
- name: Find existing comment
|
||||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
|
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
||||||
if: steps.generate-comment.outcome == 'success'
|
if: steps.generate-comment.outcome == 'success'
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/pr-comment.yaml
vendored
2
.github/workflows/pr-comment.yaml
vendored
@@ -70,7 +70,7 @@ jobs:
|
|||||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Find existing comment
|
- name: Find existing comment
|
||||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
|
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
||||||
if: steps.generate-comment.outcome == 'success'
|
if: steps.generate-comment.outcome == 'success'
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/publish-playground.yml
vendored
2
.github/workflows/publish-playground.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|||||||
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
pattern: wheels-*
|
pattern: wheels-*
|
||||||
path: wheels
|
path: wheels
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Publish the Red Knot playground.
|
# Publish the ty playground.
|
||||||
name: "[Knot Playground] Release"
|
name: "[ty Playground] Release"
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
@@ -7,12 +7,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- "crates/red_knot*/**"
|
- "crates/ty*/**"
|
||||||
- "crates/ruff_db/**"
|
- "crates/ruff_db/**"
|
||||||
- "crates/ruff_python_ast/**"
|
- "crates/ruff_python_ast/**"
|
||||||
- "crates/ruff_python_parser/**"
|
- "crates/ruff_python_parser/**"
|
||||||
- "playground/**"
|
- "playground/**"
|
||||||
- ".github/workflows/publish-knot-playground.yml"
|
- ".github/workflows/publish-ty-playground.yml"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
@@ -45,8 +45,8 @@ jobs:
|
|||||||
- name: "Run TypeScript checks"
|
- name: "Run TypeScript checks"
|
||||||
run: npm run check
|
run: npm run check
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Build Knot playground"
|
- name: "Build ty playground"
|
||||||
run: npm run build --workspace knot-playground
|
run: npm run build --workspace ty-playground
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Deploy to Cloudflare Pages"
|
- name: "Deploy to Cloudflare Pages"
|
||||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
@@ -55,4 +55,4 @@ jobs:
|
|||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||||
command: pages deploy playground/knot/dist --project-name=knot-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
command: pages deploy playground/ty/dist --project-name=ty-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||||
2
.github/workflows/publish-wasm.yml
vendored
2
.github/workflows/publish-wasm.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json crates/ruff_wasm/pkg
|
mv /tmp/package.json crates/ruff_wasm/pkg
|
||||||
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|||||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -40,6 +40,7 @@ permissions:
|
|||||||
# If there's a prerelease-style suffix to the version, then the release(s)
|
# If there's a prerelease-style suffix to the version, then the release(s)
|
||||||
# will be marked as a prerelease.
|
# will be marked as a prerelease.
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -60,7 +61,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -68,9 +69,9 @@ jobs:
|
|||||||
# we specify bash to get pipefail; it guards against the `curl` command
|
# we specify bash to get pipefail; it guards against the `curl` command
|
||||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||||
shell: bash
|
shell: bash
|
||||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4-prerelease.1/cargo-dist-installer.sh | sh"
|
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
|
||||||
- name: Cache dist
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/dist
|
path: ~/.cargo/bin/dist
|
||||||
@@ -86,7 +87,7 @@ jobs:
|
|||||||
cat plan-dist-manifest.json
|
cat plan-dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||||
with:
|
with:
|
||||||
name: artifacts-plan-dist-manifest
|
name: artifacts-plan-dist-manifest
|
||||||
path: plan-dist-manifest.json
|
path: plan-dist-manifest.json
|
||||||
@@ -123,19 +124,19 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -153,7 +154,7 @@ jobs:
|
|||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-global
|
name: artifacts-build-global
|
||||||
path: |
|
path: |
|
||||||
@@ -174,19 +175,19 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
val: ${{ steps.host.outputs.manifest }}
|
val: ${{ steps.host.outputs.manifest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Fetch artifacts from scratch-storage
|
# Fetch artifacts from scratch-storage
|
||||||
- name: Fetch artifacts
|
- name: Fetch artifacts
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -200,7 +201,7 @@ jobs:
|
|||||||
cat dist-manifest.json
|
cat dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||||
with:
|
with:
|
||||||
# Overwrite the previous copy
|
# Overwrite the previous copy
|
||||||
name: artifacts-dist-manifest
|
name: artifacts-dist-manifest
|
||||||
@@ -250,13 +251,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
# Create a GitHub Release while uploading all files to it
|
# Create a GitHub Release while uploading all files to it
|
||||||
- name: "Download GitHub Artifacts"
|
- name: "Download GitHub Artifacts"
|
||||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|||||||
16
.github/workflows/sync_typeshed.yaml
vendored
16
.github/workflows/sync_typeshed.yaml
vendored
@@ -39,13 +39,13 @@ jobs:
|
|||||||
- name: Sync typeshed
|
- name: Sync typeshed
|
||||||
id: sync
|
id: sync
|
||||||
run: |
|
run: |
|
||||||
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed
|
rm -rf ruff/crates/ty_vendored/vendor/typeshed
|
||||||
mkdir ruff/crates/red_knot_vendored/vendor/typeshed
|
mkdir ruff/crates/ty_vendored/vendor/typeshed
|
||||||
cp typeshed/README.md ruff/crates/red_knot_vendored/vendor/typeshed
|
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
|
||||||
cp typeshed/LICENSE ruff/crates/red_knot_vendored/vendor/typeshed
|
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
|
||||||
cp -r typeshed/stdlib ruff/crates/red_knot_vendored/vendor/typeshed/stdlib
|
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
|
||||||
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed/stdlib/@tests
|
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
|
||||||
git -C typeshed rev-parse HEAD > ruff/crates/red_knot_vendored/vendor/typeshed/source_commit.txt
|
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
|
||||||
- name: Commit the changes
|
- name: Commit the changes
|
||||||
id: commit
|
id: commit
|
||||||
if: ${{ steps.sync.outcome == 'success' }}
|
if: ${{ steps.sync.outcome == 'success' }}
|
||||||
@@ -79,5 +79,5 @@ jobs:
|
|||||||
repo: "ruff",
|
repo: "ruff",
|
||||||
title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
|
title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
|
||||||
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
||||||
labels: ["bug", "red-knot"],
|
labels: ["bug", "ty"],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,3 +29,7 @@ MD024:
|
|||||||
#
|
#
|
||||||
# Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854
|
# Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854
|
||||||
MD046: false
|
MD046: false
|
||||||
|
|
||||||
|
# Link text should be descriptive
|
||||||
|
# Disallows link text like *here* which is annoying.
|
||||||
|
MD059: false
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ fail_fast: false
|
|||||||
exclude: |
|
exclude: |
|
||||||
(?x)^(
|
(?x)^(
|
||||||
.github/workflows/release.yml|
|
.github/workflows/release.yml|
|
||||||
crates/red_knot_vendored/vendor/.*|
|
crates/ty_vendored/vendor/.*|
|
||||||
crates/red_knot_project/resources/.*|
|
crates/ty_project/resources/.*|
|
||||||
|
crates/ty/docs/(configuration|rules|cli).md|
|
||||||
crates/ruff_benchmark/resources/.*|
|
crates/ruff_benchmark/resources/.*|
|
||||||
crates/ruff_linter/resources/.*|
|
crates/ruff_linter/resources/.*|
|
||||||
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
||||||
@@ -42,7 +43,7 @@ repos:
|
|||||||
)$
|
)$
|
||||||
|
|
||||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||||
rev: v0.44.0
|
rev: v0.45.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: markdownlint-fix
|
- id: markdownlint-fix
|
||||||
exclude: |
|
exclude: |
|
||||||
@@ -65,7 +66,7 @@ repos:
|
|||||||
- black==25.1.0
|
- black==25.1.0
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.31.1
|
rev: v1.32.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ repos:
|
|||||||
pass_filenames: false # This makes it a lot faster
|
pass_filenames: false # This makes it a lot faster
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.6
|
rev: v0.11.10
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@@ -97,7 +98,7 @@ repos:
|
|||||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
rev: v1.6.0
|
rev: v1.7.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|||||||
158
CHANGELOG.md
158
CHANGELOG.md
@@ -1,5 +1,163 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.11.11
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`airflow`\] Add autofixes for `AIR302` and `AIR312` ([#17942](https://github.com/astral-sh/ruff/pull/17942))
|
||||||
|
- \[`airflow`\] Move rules from `AIR312` to `AIR302` ([#17940](https://github.com/astral-sh/ruff/pull/17940))
|
||||||
|
- \[`airflow`\] Update `AIR301` and `AIR311` with the latest Airflow implementations ([#17985](https://github.com/astral-sh/ruff/pull/17985))
|
||||||
|
- \[`flake8-simplify`\] Enable fix in preview mode (`SIM117`) ([#18208](https://github.com/astral-sh/ruff/pull/18208))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix inconsistent formatting of match-case on `[]` and `_` ([#18147](https://github.com/astral-sh/ruff/pull/18147))
|
||||||
|
- \[`pylint`\] Fix `PLW1514` not recognizing the `encoding` positional argument of `codecs.open` ([#18109](https://github.com/astral-sh/ruff/pull/18109))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Add full option name in formatter warning ([#18217](https://github.com/astral-sh/ruff/pull/18217))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fix rendering of admonition in docs ([#18163](https://github.com/astral-sh/ruff/pull/18163))
|
||||||
|
- \[`flake8-print`\] Improve print/pprint docs for `T201` and `T203` ([#18130](https://github.com/astral-sh/ruff/pull/18130))
|
||||||
|
- \[`flake8-simplify`\] Add fix safety section (`SIM110`,`SIM210`) ([#18114](https://github.com/astral-sh/ruff/pull/18114),[#18100](https://github.com/astral-sh/ruff/pull/18100))
|
||||||
|
- \[`pylint`\] Fix docs example that produced different output (`PLW0603`) ([#18216](https://github.com/astral-sh/ruff/pull/18216))
|
||||||
|
|
||||||
|
## 0.11.10
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`ruff`\] Implement a recursive check for `RUF060` ([#17976](https://github.com/astral-sh/ruff/pull/17976))
|
||||||
|
- \[`airflow`\] Enable autofixes for `AIR301` and `AIR311` ([#17941](https://github.com/astral-sh/ruff/pull/17941))
|
||||||
|
- \[`airflow`\] Apply try catch guard to all `AIR3` rules ([#17887](https://github.com/astral-sh/ruff/pull/17887))
|
||||||
|
- \[`airflow`\] Extend `AIR311` rules ([#17913](https://github.com/astral-sh/ruff/pull/17913))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- \[`flake8-bugbear`\] Ignore `B028` if `skip_file_prefixes` is present ([#18047](https://github.com/astral-sh/ruff/pull/18047))
|
||||||
|
- \[`flake8-pie`\] Mark autofix for `PIE804` as unsafe if the dictionary contains comments ([#18046](https://github.com/astral-sh/ruff/pull/18046))
|
||||||
|
- \[`flake8-simplify`\] Correct behavior for `str.split`/`rsplit` with `maxsplit=0` (`SIM905`) ([#18075](https://github.com/astral-sh/ruff/pull/18075))
|
||||||
|
- \[`flake8-simplify`\] Fix `SIM905` autofix for `rsplit` creating a reversed list literal ([#18045](https://github.com/astral-sh/ruff/pull/18045))
|
||||||
|
- \[`flake8-use-pathlib`\] Suppress diagnostics for all `os.*` functions that have the `dir_fd` parameter (`PTH`) ([#17968](https://github.com/astral-sh/ruff/pull/17968))
|
||||||
|
- \[`refurb`\] Mark autofix as safe only for number literals (`FURB116`) ([#17692](https://github.com/astral-sh/ruff/pull/17692))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`flake8-bandit`\] Skip `S608` for expressionless f-strings ([#17999](https://github.com/astral-sh/ruff/pull/17999))
|
||||||
|
- \[`flake8-pytest-style`\] Don't recommend `usefixtures` for `parametrize` values (`PT019`) ([#17650](https://github.com/astral-sh/ruff/pull/17650))
|
||||||
|
- \[`pyupgrade`\] Add `resource.error` as deprecated alias of `OSError` (`UP024`) ([#17933](https://github.com/astral-sh/ruff/pull/17933))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Disable jemalloc on Android ([#18033](https://github.com/astral-sh/ruff/pull/18033))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Update Neovim setup docs ([#18108](https://github.com/astral-sh/ruff/pull/18108))
|
||||||
|
- \[`flake8-simplify`\] Add fix safety section (`SIM103`) ([#18086](https://github.com/astral-sh/ruff/pull/18086))
|
||||||
|
- \[`flake8-simplify`\] Add fix safety section (`SIM112`) ([#18099](https://github.com/astral-sh/ruff/pull/18099))
|
||||||
|
- \[`pylint`\] Add fix safety section (`PLC0414`) ([#17802](https://github.com/astral-sh/ruff/pull/17802))
|
||||||
|
- \[`pylint`\] Add fix safety section (`PLE4703`) ([#17824](https://github.com/astral-sh/ruff/pull/17824))
|
||||||
|
- \[`pylint`\] Add fix safety section (`PLW1514`) ([#17932](https://github.com/astral-sh/ruff/pull/17932))
|
||||||
|
- \[`pylint`\] Add fix safety section (`PLW3301`) ([#17878](https://github.com/astral-sh/ruff/pull/17878))
|
||||||
|
- \[`ruff`\] Add fix safety section (`RUF007`) ([#17755](https://github.com/astral-sh/ruff/pull/17755))
|
||||||
|
- \[`ruff`\] Add fix safety section (`RUF033`) ([#17760](https://github.com/astral-sh/ruff/pull/17760))
|
||||||
|
|
||||||
|
## 0.11.9
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- Default to latest supported Python version for version-related syntax errors ([#17529](https://github.com/astral-sh/ruff/pull/17529))
|
||||||
|
- Implement deferred annotations for Python 3.14 ([#17658](https://github.com/astral-sh/ruff/pull/17658))
|
||||||
|
- \[`airflow`\] Fix `SQLTableCheckOperator` typo (`AIR302`) ([#17946](https://github.com/astral-sh/ruff/pull/17946))
|
||||||
|
- \[`airflow`\] Remove `airflow.utils.dag_parsing_context.get_parsing_context` (`AIR301`) ([#17852](https://github.com/astral-sh/ruff/pull/17852))
|
||||||
|
- \[`airflow`\] Skip attribute check in try catch block (`AIR301`) ([#17790](https://github.com/astral-sh/ruff/pull/17790))
|
||||||
|
- \[`flake8-bandit`\] Mark tuples of string literals as trusted input in `S603` ([#17801](https://github.com/astral-sh/ruff/pull/17801))
|
||||||
|
- \[`isort`\] Check full module path against project root(s) when categorizing first-party imports ([#16565](https://github.com/astral-sh/ruff/pull/16565))
|
||||||
|
- \[`ruff`\] Add new rule `in-empty-collection` (`RUF060`) ([#16480](https://github.com/astral-sh/ruff/pull/16480))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix missing `combine` call for `lint.typing-extensions` setting ([#17823](https://github.com/astral-sh/ruff/pull/17823))
|
||||||
|
- \[`flake8-async`\] Fix module name in `ASYNC110`, `ASYNC115`, and `ASYNC116` fixes ([#17774](https://github.com/astral-sh/ruff/pull/17774))
|
||||||
|
- \[`pyupgrade`\] Add spaces between tokens as necessary to avoid syntax errors in `UP018` autofix ([#17648](https://github.com/astral-sh/ruff/pull/17648))
|
||||||
|
- \[`refurb`\] Fix false positive for float and complex numbers in `FURB116` ([#17661](https://github.com/astral-sh/ruff/pull/17661))
|
||||||
|
- [parser] Flag single unparenthesized generator expr with trailing comma in arguments. ([#17893](https://github.com/astral-sh/ruff/pull/17893))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add instructions on how to upgrade to a newer Rust version ([#17928](https://github.com/astral-sh/ruff/pull/17928))
|
||||||
|
- Update code of conduct email address ([#17875](https://github.com/astral-sh/ruff/pull/17875))
|
||||||
|
- Add fix safety sections to `PLC2801`, `PLR1722`, and `RUF013` ([#17825](https://github.com/astral-sh/ruff/pull/17825), [#17826](https://github.com/astral-sh/ruff/pull/17826), [#17759](https://github.com/astral-sh/ruff/pull/17759))
|
||||||
|
- Add link to `check-typed-exception` from `S110` and `S112` ([#17786](https://github.com/astral-sh/ruff/pull/17786))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- Allow passing a virtual environment to `ruff analyze graph` ([#17743](https://github.com/astral-sh/ruff/pull/17743))
|
||||||
|
|
||||||
|
## 0.11.8
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR302`, `AIR311`) ([#17553](https://github.com/astral-sh/ruff/pull/17553), [#17570](https://github.com/astral-sh/ruff/pull/17570), [#17571](https://github.com/astral-sh/ruff/pull/17571))
|
||||||
|
- \[`airflow`\] Extend `AIR301` rule ([#17598](https://github.com/astral-sh/ruff/pull/17598))
|
||||||
|
- \[`airflow`\] Update existing `AIR302` rules with better suggestions ([#17542](https://github.com/astral-sh/ruff/pull/17542))
|
||||||
|
- \[`refurb`\] Mark fix as safe for `readlines-in-for` (`FURB129`) ([#17644](https://github.com/astral-sh/ruff/pull/17644))
|
||||||
|
- [syntax-errors] `nonlocal` declaration at module level ([#17559](https://github.com/astral-sh/ruff/pull/17559))
|
||||||
|
- [syntax-errors] Detect single starred expression assignment `x = *y` ([#17624](https://github.com/astral-sh/ruff/pull/17624))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- \[`flake8-pyi`\] Ensure `Literal[None,] | Literal[None,]` is not autofixed to `None | None` (`PYI061`) ([#17659](https://github.com/astral-sh/ruff/pull/17659))
|
||||||
|
- \[`flake8-use-pathlib`\] Avoid suggesting `Path.iterdir()` for `os.listdir` with file descriptor (`PTH208`) ([#17715](https://github.com/astral-sh/ruff/pull/17715))
|
||||||
|
- \[`flake8-use-pathlib`\] Fix `PTH104` false positive when `rename` is passed a file descriptor ([#17712](https://github.com/astral-sh/ruff/pull/17712))
|
||||||
|
- \[`flake8-use-pathlib`\] Fix `PTH116` false positive when `stat` is passed a file descriptor ([#17709](https://github.com/astral-sh/ruff/pull/17709))
|
||||||
|
- \[`flake8-use-pathlib`\] Fix `PTH123` false positive when `open` is passed a file descriptor from a function call ([#17705](https://github.com/astral-sh/ruff/pull/17705))
|
||||||
|
- \[`pycodestyle`\] Fix duplicated diagnostic in `E712` ([#17651](https://github.com/astral-sh/ruff/pull/17651))
|
||||||
|
- \[`pylint`\] Detect `global` declarations in module scope (`PLE0118`) ([#17411](https://github.com/astral-sh/ruff/pull/17411))
|
||||||
|
- [syntax-errors] Make `async-comprehension-in-sync-comprehension` more specific ([#17460](https://github.com/astral-sh/ruff/pull/17460))
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Add option to disable `typing_extensions` imports ([#17611](https://github.com/astral-sh/ruff/pull/17611))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fix example syntax for the `lint.pydocstyle.ignore-var-parameters` option ([#17740](https://github.com/astral-sh/ruff/pull/17740))
|
||||||
|
- Add fix safety sections (`ASYNC116`, `FLY002`, `D200`, `RUF005`, `RUF017`, `RUF027`, `RUF028`, `RUF057`) ([#17497](https://github.com/astral-sh/ruff/pull/17497), [#17496](https://github.com/astral-sh/ruff/pull/17496), [#17502](https://github.com/astral-sh/ruff/pull/17502), [#17484](https://github.com/astral-sh/ruff/pull/17484), [#17480](https://github.com/astral-sh/ruff/pull/17480), [#17485](https://github.com/astral-sh/ruff/pull/17485), [#17722](https://github.com/astral-sh/ruff/pull/17722), [#17483](https://github.com/astral-sh/ruff/pull/17483))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- Add Python 3.14 to configuration options ([#17647](https://github.com/astral-sh/ruff/pull/17647))
|
||||||
|
- Make syntax error for unparenthesized except tuples version specific to before 3.14 ([#17660](https://github.com/astral-sh/ruff/pull/17660))
|
||||||
|
|
||||||
|
## 0.11.7
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) ([#17355](https://github.com/astral-sh/ruff/pull/17355))
|
||||||
|
- \[`perflint`\] Implement fix for `manual-dict-comprehension` (`PERF403`) ([#16719](https://github.com/astral-sh/ruff/pull/16719))
|
||||||
|
- [syntax-errors] Make duplicate parameter names a semantic error ([#17131](https://github.com/astral-sh/ruff/pull/17131))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- \[`airflow`\] Fix typos in provider package names (`AIR302`, `AIR312`) ([#17574](https://github.com/astral-sh/ruff/pull/17574))
|
||||||
|
- \[`flake8-type-checking`\] Visit keyword arguments in checks involving `typing.cast`/`typing.NewType` arguments ([#17538](https://github.com/astral-sh/ruff/pull/17538))
|
||||||
|
- \[`pyupgrade`\] Preserve parenthesis when fixing native literals containing newlines (`UP018`) ([#17220](https://github.com/astral-sh/ruff/pull/17220))
|
||||||
|
- \[`refurb`\] Mark the `FURB161` fix unsafe except for integers and booleans ([#17240](https://github.com/astral-sh/ruff/pull/17240))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`perflint`\] Allow list function calls to be replaced with a comprehension (`PERF401`) ([#17519](https://github.com/astral-sh/ruff/pull/17519))
|
||||||
|
- \[`pycodestyle`\] Auto-fix redundant boolean comparison (`E712`) ([#17090](https://github.com/astral-sh/ruff/pull/17090))
|
||||||
|
- \[`pylint`\] make fix unsafe if delete comments (`PLR1730`) ([#17459](https://github.com/astral-sh/ruff/pull/17459))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add fix safety sections to docs for several rules ([#17410](https://github.com/astral-sh/ruff/pull/17410),[#17440](https://github.com/astral-sh/ruff/pull/17440),[#17441](https://github.com/astral-sh/ruff/pull/17441),[#17443](https://github.com/astral-sh/ruff/pull/17443),[#17444](https://github.com/astral-sh/ruff/pull/17444))
|
||||||
|
|
||||||
## 0.11.6
|
## 0.11.6
|
||||||
|
|
||||||
### Preview features
|
### Preview features
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ representative at an online or offline event.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at <hey@astral.sh>.
|
||||||
<charlie.r.marsh@gmail.com>.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing
|
||||||
|
> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
|
||||||
|
|
||||||
## The Basics
|
## The Basics
|
||||||
|
|
||||||
Ruff welcomes contributions in the form of pull requests.
|
Ruff welcomes contributions in the form of pull requests.
|
||||||
@@ -366,6 +371,15 @@ uvx --from ./python/ruff-ecosystem ruff-ecosystem format ruff "./target/debug/ru
|
|||||||
|
|
||||||
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
|
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
|
||||||
|
|
||||||
|
## Upgrading Rust
|
||||||
|
|
||||||
|
1. Change the `channel` in `./rust-toolchain.toml` to the new Rust version (`<latest>`)
|
||||||
|
1. Change the `rust-version` in the `./Cargo.toml` to `<latest> - 2` (e.g. 1.84 if the latest is 1.86)
|
||||||
|
1. Run `cargo clippy --fix --allow-dirty --allow-staged` to fix new clippy warnings
|
||||||
|
1. Create and merge the PR
|
||||||
|
1. Bump the Rust version in Ruff's conda forge recipe. See [this PR](https://github.com/conda-forge/ruff-feedstock/pull/266) for an example.
|
||||||
|
1. Enjoy the new Rust version!
|
||||||
|
|
||||||
## Benchmarking and Profiling
|
## Benchmarking and Profiling
|
||||||
|
|
||||||
We have several ways of benchmarking and profiling Ruff:
|
We have several ways of benchmarking and profiling Ruff:
|
||||||
@@ -397,7 +411,7 @@ cargo install hyperfine
|
|||||||
To benchmark the release build:
|
To benchmark the release build:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release && hyperfine --warmup 10 \
|
cargo build --release --bin ruff && hyperfine --warmup 10 \
|
||||||
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ --no-cache -e" \
|
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ --no-cache -e" \
|
||||||
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ -e"
|
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ -e"
|
||||||
|
|
||||||
@@ -596,8 +610,7 @@ Then convert the recorded profile
|
|||||||
perf script -F +pid > /tmp/test.perf
|
perf script -F +pid > /tmp/test.perf
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now view the converted file with [firefox profiler](https://profiler.firefox.com/), with a
|
You can now view the converted file with [firefox profiler](https://profiler.firefox.com/). To learn more about Firefox profiler, read the [Firefox profiler profiling-guide](https://profiler.firefox.com/docs/#/./guide-perf-profiling).
|
||||||
more in-depth guide [here](https://profiler.firefox.com/docs/#/./guide-perf-profiling)
|
|
||||||
|
|
||||||
An alternative is to convert the perf data to `flamegraph.svg` using
|
An alternative is to convert the perf data to `flamegraph.svg` using
|
||||||
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
||||||
|
|||||||
1330
Cargo.lock
generated
1330
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
108
Cargo.toml
108
Cargo.toml
@@ -3,8 +3,9 @@ members = ["crates/*"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
# Please update rustfmt.toml when bumping the Rust edition
|
||||||
rust-version = "1.84"
|
edition = "2024"
|
||||||
|
rust-version = "1.85"
|
||||||
homepage = "https://docs.astral.sh/ruff"
|
homepage = "https://docs.astral.sh/ruff"
|
||||||
documentation = "https://docs.astral.sh/ruff"
|
documentation = "https://docs.astral.sh/ruff"
|
||||||
repository = "https://github.com/astral-sh/ruff"
|
repository = "https://github.com/astral-sh/ruff"
|
||||||
@@ -23,6 +24,7 @@ ruff_index = { path = "crates/ruff_index" }
|
|||||||
ruff_linter = { path = "crates/ruff_linter" }
|
ruff_linter = { path = "crates/ruff_linter" }
|
||||||
ruff_macros = { path = "crates/ruff_macros" }
|
ruff_macros = { path = "crates/ruff_macros" }
|
||||||
ruff_notebook = { path = "crates/ruff_notebook" }
|
ruff_notebook = { path = "crates/ruff_notebook" }
|
||||||
|
ruff_options_metadata = { path = "crates/ruff_options_metadata" }
|
||||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||||
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
||||||
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
||||||
@@ -35,14 +37,15 @@ ruff_python_trivia = { path = "crates/ruff_python_trivia" }
|
|||||||
ruff_server = { path = "crates/ruff_server" }
|
ruff_server = { path = "crates/ruff_server" }
|
||||||
ruff_source_file = { path = "crates/ruff_source_file" }
|
ruff_source_file = { path = "crates/ruff_source_file" }
|
||||||
ruff_text_size = { path = "crates/ruff_text_size" }
|
ruff_text_size = { path = "crates/ruff_text_size" }
|
||||||
red_knot_vendored = { path = "crates/red_knot_vendored" }
|
|
||||||
ruff_workspace = { path = "crates/ruff_workspace" }
|
ruff_workspace = { path = "crates/ruff_workspace" }
|
||||||
|
|
||||||
red_knot_ide = { path = "crates/red_knot_ide" }
|
ty = { path = "crates/ty" }
|
||||||
red_knot_project = { path = "crates/red_knot_project", default-features = false }
|
ty_ide = { path = "crates/ty_ide" }
|
||||||
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
|
ty_project = { path = "crates/ty_project", default-features = false }
|
||||||
red_knot_server = { path = "crates/red_knot_server" }
|
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||||
red_knot_test = { path = "crates/red_knot_test" }
|
ty_server = { path = "crates/ty_server" }
|
||||||
|
ty_test = { path = "crates/ty_test" }
|
||||||
|
ty_vendored = { path = "crates/ty_vendored" }
|
||||||
|
|
||||||
aho-corasick = { version = "1.1.3" }
|
aho-corasick = { version = "1.1.3" }
|
||||||
anstream = { version = "0.6.18" }
|
anstream = { version = "0.6.18" }
|
||||||
@@ -50,7 +53,7 @@ anstyle = { version = "1.0.10" }
|
|||||||
anyhow = { version = "1.0.80" }
|
anyhow = { version = "1.0.80" }
|
||||||
assert_fs = { version = "1.1.0" }
|
assert_fs = { version = "1.1.0" }
|
||||||
argfile = { version = "0.2.0" }
|
argfile = { version = "0.2.0" }
|
||||||
bincode = { version = "1.3.3" }
|
bincode = { version = "2.0.0" }
|
||||||
bitflags = { version = "2.5.0" }
|
bitflags = { version = "2.5.0" }
|
||||||
bstr = { version = "1.9.1" }
|
bstr = { version = "1.9.1" }
|
||||||
cachedir = { version = "0.3.1" }
|
cachedir = { version = "0.3.1" }
|
||||||
@@ -64,7 +67,7 @@ console_error_panic_hook = { version = "0.1.7" }
|
|||||||
console_log = { version = "1.0.0" }
|
console_log = { version = "1.0.0" }
|
||||||
countme = { version = "3.0.1" }
|
countme = { version = "3.0.1" }
|
||||||
compact_str = "0.9.0"
|
compact_str = "0.9.0"
|
||||||
criterion = { version = "0.5.1", default-features = false }
|
criterion = { version = "0.6.0", default-features = false }
|
||||||
crossbeam = { version = "0.8.4" }
|
crossbeam = { version = "0.8.4" }
|
||||||
dashmap = { version = "6.0.1" }
|
dashmap = { version = "6.0.1" }
|
||||||
dir-test = { version = "0.4.0" }
|
dir-test = { version = "0.4.0" }
|
||||||
@@ -83,6 +86,7 @@ hashbrown = { version = "0.15.0", default-features = false, features = [
|
|||||||
"equivalent",
|
"equivalent",
|
||||||
"inline-more",
|
"inline-more",
|
||||||
] }
|
] }
|
||||||
|
heck = "0.5.0"
|
||||||
ignore = { version = "0.4.22" }
|
ignore = { version = "0.4.22" }
|
||||||
imara-diff = { version = "0.1.5" }
|
imara-diff = { version = "0.1.5" }
|
||||||
imperative = { version = "1.0.4" }
|
imperative = { version = "1.0.4" }
|
||||||
@@ -96,7 +100,7 @@ is-wsl = { version = "0.4.0" }
|
|||||||
itertools = { version = "0.14.0" }
|
itertools = { version = "0.14.0" }
|
||||||
jiff = { version = "0.2.0" }
|
jiff = { version = "0.2.0" }
|
||||||
js-sys = { version = "0.3.69" }
|
js-sys = { version = "0.3.69" }
|
||||||
jod-thread = { version = "0.1.2" }
|
jod-thread = { version = "1.0.0" }
|
||||||
libc = { version = "0.2.153" }
|
libc = { version = "0.2.153" }
|
||||||
libcst = { version = "1.1.0", default-features = false }
|
libcst = { version = "1.1.0", default-features = false }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
@@ -123,8 +127,9 @@ rand = { version = "0.9.0" }
|
|||||||
rayon = { version = "1.10.0" }
|
rayon = { version = "1.10.0" }
|
||||||
regex = { version = "1.10.2" }
|
regex = { version = "1.10.2" }
|
||||||
rustc-hash = { version = "2.0.0" }
|
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`
|
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "87bf6b6c2d5f6479741271da73bd9d30c2580c26" }
|
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "4818b15f3b7516555d39f5a41cb75970448bee4c" }
|
||||||
schemars = { version = "0.8.16" }
|
schemars = { version = "0.8.16" }
|
||||||
seahash = { version = "4.1.0" }
|
seahash = { version = "4.1.0" }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
@@ -159,8 +164,9 @@ tracing-log = { version = "0.2.0" }
|
|||||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
||||||
"env-filter",
|
"env-filter",
|
||||||
"fmt",
|
"fmt",
|
||||||
|
"ansi",
|
||||||
|
"smallvec"
|
||||||
] }
|
] }
|
||||||
tracing-tree = { version = "0.4.0" }
|
|
||||||
tryfn = { version = "0.2.1" }
|
tryfn = { version = "0.2.1" }
|
||||||
typed-arena = { version = "2.0.2" }
|
typed-arena = { version = "2.0.2" }
|
||||||
unic-ucd-category = { version = "0.9" }
|
unic-ucd-category = { version = "0.9" }
|
||||||
@@ -182,7 +188,7 @@ wild = { version = "2" }
|
|||||||
zip = { version = "0.6.6", default-features = false }
|
zip = { version = "0.6.6", default-features = false }
|
||||||
|
|
||||||
[workspace.metadata.cargo-shear]
|
[workspace.metadata.cargo-shear]
|
||||||
ignored = ["getrandom"]
|
ignored = ["getrandom", "ruff_options_metadata"]
|
||||||
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
@@ -210,6 +216,7 @@ similar_names = "allow"
|
|||||||
single_match_else = "allow"
|
single_match_else = "allow"
|
||||||
too_many_lines = "allow"
|
too_many_lines = "allow"
|
||||||
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
||||||
|
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
|
||||||
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
|
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
|
||||||
needless_raw_string_hashes = "allow"
|
needless_raw_string_hashes = "allow"
|
||||||
# Disallowed restriction lints
|
# Disallowed restriction lints
|
||||||
@@ -254,6 +261,9 @@ opt-level = 3
|
|||||||
[profile.dev.package.similar]
|
[profile.dev.package.similar]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev.package.salsa]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
# Reduce complexity of a parser function that would trigger a locals limit in a wasm tool.
|
# Reduce complexity of a parser function that would trigger a locals limit in a wasm tool.
|
||||||
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
|
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
|
||||||
[profile.dev.package.ruff_python_parser]
|
[profile.dev.package.ruff_python_parser]
|
||||||
@@ -268,73 +278,3 @@ debug = 1
|
|||||||
# The profile that 'cargo dist' will build with.
|
# The profile that 'cargo dist' will build with.
|
||||||
[profile.dist]
|
[profile.dist]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|
||||||
# Config for 'dist'
|
|
||||||
[workspace.metadata.dist]
|
|
||||||
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
|
||||||
cargo-dist-version = "0.28.4-prerelease.1"
|
|
||||||
# CI backends to support
|
|
||||||
ci = "github"
|
|
||||||
# The installers to generate for each app
|
|
||||||
installers = ["shell", "powershell"]
|
|
||||||
# The archive format to use for windows builds (defaults .zip)
|
|
||||||
windows-archive = ".zip"
|
|
||||||
# The archive format to use for non-windows builds (defaults .tar.xz)
|
|
||||||
unix-archive = ".tar.gz"
|
|
||||||
# Target platforms to build apps for (Rust target-triple syntax)
|
|
||||||
targets = [
|
|
||||||
"aarch64-apple-darwin",
|
|
||||||
"aarch64-pc-windows-msvc",
|
|
||||||
"aarch64-unknown-linux-gnu",
|
|
||||||
"aarch64-unknown-linux-musl",
|
|
||||||
"arm-unknown-linux-musleabihf",
|
|
||||||
"armv7-unknown-linux-gnueabihf",
|
|
||||||
"armv7-unknown-linux-musleabihf",
|
|
||||||
"i686-pc-windows-msvc",
|
|
||||||
"i686-unknown-linux-gnu",
|
|
||||||
"i686-unknown-linux-musl",
|
|
||||||
"powerpc64-unknown-linux-gnu",
|
|
||||||
"powerpc64le-unknown-linux-gnu",
|
|
||||||
"s390x-unknown-linux-gnu",
|
|
||||||
"x86_64-apple-darwin",
|
|
||||||
"x86_64-pc-windows-msvc",
|
|
||||||
"x86_64-unknown-linux-gnu",
|
|
||||||
"x86_64-unknown-linux-musl",
|
|
||||||
]
|
|
||||||
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
|
|
||||||
auto-includes = false
|
|
||||||
# Whether dist should create a Github Release or use an existing draft
|
|
||||||
create-release = true
|
|
||||||
# Which actions to run on pull requests
|
|
||||||
pr-run-mode = "skip"
|
|
||||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
|
||||||
dispatch-releases = true
|
|
||||||
# Which phase dist should use to create the GitHub release
|
|
||||||
github-release = "announce"
|
|
||||||
# Whether CI should include auto-generated code to build local artifacts
|
|
||||||
build-local-artifacts = false
|
|
||||||
# Local artifacts jobs to run in CI
|
|
||||||
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
|
||||||
# Publish jobs to run in CI
|
|
||||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
|
||||||
# Post-announce jobs to run in CI
|
|
||||||
post-announce-jobs = [
|
|
||||||
"./notify-dependents",
|
|
||||||
"./publish-docs",
|
|
||||||
"./publish-playground",
|
|
||||||
]
|
|
||||||
# Custom permissions for GitHub Jobs
|
|
||||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" } }
|
|
||||||
# Whether to install an updater program
|
|
||||||
install-updater = false
|
|
||||||
# Path that installers should place binaries in
|
|
||||||
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
|
||||||
|
|
||||||
[workspace.metadata.dist.github-custom-runners]
|
|
||||||
global = "depot-ubuntu-latest-4"
|
|
||||||
|
|
||||||
[workspace.metadata.dist.github-action-commits]
|
|
||||||
"actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4
|
|
||||||
"actions/upload-artifact" = "ea165f8d65b6e75b540449e92b4886f43607fa02" # v4.6.2
|
|
||||||
"actions/download-artifact" = "95815c38cf2ff2164869cbab79da8d1f422bc89e" # v4.2.1
|
|
||||||
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
|||||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||||
|
|
||||||
# For a specific version.
|
# For a specific version.
|
||||||
curl -LsSf https://astral.sh/ruff/0.11.6/install.sh | sh
|
curl -LsSf https://astral.sh/ruff/0.11.11/install.sh | sh
|
||||||
powershell -c "irm https://astral.sh/ruff/0.11.6/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/0.11.11/install.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||||
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
|||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.11.6
|
rev: v0.11.11
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@@ -255,7 +255,7 @@ indent-width = 4
|
|||||||
target-version = "py39"
|
target-version = "py39"
|
||||||
|
|
||||||
[lint]
|
[lint]
|
||||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
select = ["E4", "E7", "E9", "F"]
|
select = ["E4", "E7", "E9", "F"]
|
||||||
ignore = []
|
ignore = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[files]
|
[files]
|
||||||
# https://github.com/crate-ci/typos/issues/868
|
# https://github.com/crate-ci/typos/issues/868
|
||||||
extend-exclude = [
|
extend-exclude = [
|
||||||
"crates/red_knot_vendored/vendor/**/*",
|
"crates/ty_vendored/vendor/**/*",
|
||||||
"**/resources/**/*",
|
"**/resources/**/*",
|
||||||
"**/snapshots/**/*",
|
"**/snapshots/**/*",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
doc-valid-idents = [
|
doc-valid-idents = [
|
||||||
"..",
|
"..",
|
||||||
"CodeQL",
|
"CodeQL",
|
||||||
|
"CPython",
|
||||||
"FastAPI",
|
"FastAPI",
|
||||||
"IPython",
|
"IPython",
|
||||||
"LangChain",
|
"LangChain",
|
||||||
@@ -14,7 +15,7 @@ doc-valid-idents = [
|
|||||||
"SNMPv1",
|
"SNMPv1",
|
||||||
"SNMPv2",
|
"SNMPv2",
|
||||||
"SNMPv3",
|
"SNMPv3",
|
||||||
"PyFlakes"
|
"PyFlakes",
|
||||||
]
|
]
|
||||||
|
|
||||||
ignore-interior-mutability = [
|
ignore-interior-mutability = [
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# Red Knot
|
|
||||||
|
|
||||||
Red Knot is an extremely fast type checker.
|
|
||||||
Currently, it is a work-in-progress and not ready for user testing.
|
|
||||||
|
|
||||||
Red Knot is designed to prioritize good type inference, even in unannotated code,
|
|
||||||
and aims to avoid false positives.
|
|
||||||
|
|
||||||
While Red Knot will produce similar results to mypy and pyright on many codebases,
|
|
||||||
100% compatibility with these tools is a non-goal.
|
|
||||||
On some codebases, Red Knot's design decisions lead to different outcomes
|
|
||||||
than you would get from running one of these more established tools.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Core type checking tests are written as Markdown code blocks.
|
|
||||||
They can be found in [`red_knot_python_semantic/resources/mdtest`][resources-mdtest].
|
|
||||||
See [`red_knot_test/README.md`][mdtest-readme] for more information
|
|
||||||
on the test framework itself.
|
|
||||||
|
|
||||||
The list of open issues can be found [here][open-issues].
|
|
||||||
|
|
||||||
[mdtest-readme]: ../red_knot_test/README.md
|
|
||||||
[open-issues]: https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Ared-knot
|
|
||||||
[resources-mdtest]: ../red_knot_python_semantic/resources/mdtest
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
|||||||
# Any
|
|
||||||
|
|
||||||
## Annotation
|
|
||||||
|
|
||||||
`typing.Any` is a way to name the Any type.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
x: Any = 1
|
|
||||||
x = "foo"
|
|
||||||
|
|
||||||
def f():
|
|
||||||
reveal_type(x) # revealed: Any
|
|
||||||
```
|
|
||||||
|
|
||||||
## Aliased to a different name
|
|
||||||
|
|
||||||
If you alias `typing.Any` to another name, we still recognize that as a spelling of the Any type.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Any as RenamedAny
|
|
||||||
|
|
||||||
x: RenamedAny = 1
|
|
||||||
x = "foo"
|
|
||||||
|
|
||||||
def f():
|
|
||||||
reveal_type(x) # revealed: Any
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shadowed class
|
|
||||||
|
|
||||||
If you define your own class named `Any`, using that in a type expression refers to your class, and
|
|
||||||
isn't a spelling of the Any type.
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Any: ...
|
|
||||||
|
|
||||||
x: Any
|
|
||||||
|
|
||||||
def f():
|
|
||||||
reveal_type(x) # revealed: Any
|
|
||||||
|
|
||||||
# This verifies that we're not accidentally seeing typing.Any, since str is assignable
|
|
||||||
# to that but not to our locally defined class.
|
|
||||||
y: Any = "not an Any" # error: [invalid-assignment]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Subclass
|
|
||||||
|
|
||||||
The spec allows you to define subclasses of `Any`.
|
|
||||||
|
|
||||||
TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The
|
|
||||||
assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment
|
|
||||||
to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore
|
|
||||||
assignable to `int`.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
class Subclass(Any): ...
|
|
||||||
|
|
||||||
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
|
|
||||||
|
|
||||||
x: Subclass = 1 # error: [invalid-assignment]
|
|
||||||
# TODO: no diagnostic
|
|
||||||
y: int = Subclass() # error: [invalid-assignment]
|
|
||||||
|
|
||||||
def _(s: Subclass):
|
|
||||||
reveal_type(s) # revealed: Subclass
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invalid
|
|
||||||
|
|
||||||
`Any` cannot be parameterized:
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
# error: [invalid-type-form] "Type `typing.Any` expected no type parameter"
|
|
||||||
def f(x: Any[int]):
|
|
||||||
reveal_type(x) # revealed: Unknown
|
|
||||||
```
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# Tests for invalid types in type expressions
|
|
||||||
|
|
||||||
## Invalid types are rejected
|
|
||||||
|
|
||||||
Many types are illegal in the context of a type expression:
|
|
||||||
|
|
||||||
```py
|
|
||||||
import typing
|
|
||||||
from knot_extensions import AlwaysTruthy, AlwaysFalsy
|
|
||||||
from typing_extensions import Literal, Never
|
|
||||||
|
|
||||||
class A: ...
|
|
||||||
|
|
||||||
def _(
|
|
||||||
a: type[int],
|
|
||||||
b: AlwaysTruthy,
|
|
||||||
c: AlwaysFalsy,
|
|
||||||
d: Literal[True],
|
|
||||||
e: Literal["bar"],
|
|
||||||
f: Literal[b"foo"],
|
|
||||||
g: tuple[int, str],
|
|
||||||
h: Never,
|
|
||||||
i: int,
|
|
||||||
j: A,
|
|
||||||
):
|
|
||||||
def foo(): ...
|
|
||||||
def invalid(
|
|
||||||
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
|
||||||
b_: b, # error: [invalid-type-form]
|
|
||||||
c_: c, # error: [invalid-type-form]
|
|
||||||
d_: d, # error: [invalid-type-form]
|
|
||||||
e_: e, # error: [invalid-type-form]
|
|
||||||
f_: f, # error: [invalid-type-form]
|
|
||||||
g_: g, # error: [invalid-type-form]
|
|
||||||
h_: h, # error: [invalid-type-form]
|
|
||||||
i_: typing, # error: [invalid-type-form]
|
|
||||||
j_: foo, # error: [invalid-type-form]
|
|
||||||
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
|
|
||||||
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
|
|
||||||
):
|
|
||||||
reveal_type(a_) # revealed: Unknown
|
|
||||||
reveal_type(b_) # revealed: Unknown
|
|
||||||
reveal_type(c_) # revealed: Unknown
|
|
||||||
reveal_type(d_) # revealed: Unknown
|
|
||||||
reveal_type(e_) # revealed: Unknown
|
|
||||||
reveal_type(f_) # revealed: Unknown
|
|
||||||
reveal_type(g_) # revealed: Unknown
|
|
||||||
reveal_type(h_) # revealed: Unknown
|
|
||||||
reveal_type(i_) # revealed: Unknown
|
|
||||||
reveal_type(j_) # revealed: Unknown
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invalid AST nodes
|
|
||||||
|
|
||||||
```py
|
|
||||||
def bar() -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _(
|
|
||||||
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
|
||||||
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
|
|
||||||
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
|
|
||||||
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
|
||||||
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
|
|
||||||
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
|
||||||
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
|
||||||
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
|
||||||
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
|
|
||||||
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
|
|
||||||
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
|
|
||||||
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
|
||||||
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
|
||||||
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
|
||||||
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
|
|
||||||
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
|
||||||
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
|
|
||||||
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
|
|
||||||
):
|
|
||||||
reveal_type(a) # revealed: Unknown
|
|
||||||
reveal_type(b) # revealed: Unknown
|
|
||||||
reveal_type(c) # revealed: Unknown
|
|
||||||
reveal_type(d) # revealed: Unknown
|
|
||||||
reveal_type(e) # revealed: int | Unknown
|
|
||||||
reveal_type(f) # revealed: Unknown
|
|
||||||
reveal_type(g) # revealed: Unknown
|
|
||||||
reveal_type(h) # revealed: Unknown
|
|
||||||
reveal_type(i) # revealed: Unknown
|
|
||||||
reveal_type(j) # revealed: Unknown
|
|
||||||
reveal_type(k) # revealed: Unknown
|
|
||||||
reveal_type(p) # revealed: Unknown
|
|
||||||
reveal_type(q) # revealed: int | Unknown
|
|
||||||
reveal_type(r) # revealed: @Todo(unknown type subscript)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invalid Collection based AST nodes
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(
|
|
||||||
a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions"
|
|
||||||
b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in type expressions"
|
|
||||||
c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in type expressions"
|
|
||||||
d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions"
|
|
||||||
e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions"
|
|
||||||
f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions"
|
|
||||||
g: [int, str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
|
||||||
):
|
|
||||||
reveal_type(a) # revealed: Unknown
|
|
||||||
reveal_type(b) # revealed: Unknown
|
|
||||||
reveal_type(c) # revealed: Unknown
|
|
||||||
reveal_type(d) # revealed: Unknown
|
|
||||||
reveal_type(e) # revealed: Unknown
|
|
||||||
reveal_type(f) # revealed: Unknown
|
|
||||||
reveal_type(g) # revealed: Unknown
|
|
||||||
```
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# Typing-module aliases to other stdlib classes
|
|
||||||
|
|
||||||
The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but
|
|
||||||
still need to be supported by a type checker.
|
|
||||||
|
|
||||||
## Correspondence
|
|
||||||
|
|
||||||
All of the following symbols can be mapped one-to-one with the actual type:
|
|
||||||
|
|
||||||
```py
|
|
||||||
import typing
|
|
||||||
|
|
||||||
def f(
|
|
||||||
list_bare: typing.List,
|
|
||||||
list_parametrized: typing.List[int],
|
|
||||||
dict_bare: typing.Dict,
|
|
||||||
dict_parametrized: typing.Dict[int, str],
|
|
||||||
set_bare: typing.Set,
|
|
||||||
set_parametrized: typing.Set[int],
|
|
||||||
frozen_set_bare: typing.FrozenSet,
|
|
||||||
frozen_set_parametrized: typing.FrozenSet[str],
|
|
||||||
chain_map_bare: typing.ChainMap,
|
|
||||||
chain_map_parametrized: typing.ChainMap[int],
|
|
||||||
counter_bare: typing.Counter,
|
|
||||||
counter_parametrized: typing.Counter[int],
|
|
||||||
default_dict_bare: typing.DefaultDict,
|
|
||||||
default_dict_parametrized: typing.DefaultDict[str, int],
|
|
||||||
deque_bare: typing.Deque,
|
|
||||||
deque_parametrized: typing.Deque[str],
|
|
||||||
ordered_dict_bare: typing.OrderedDict,
|
|
||||||
ordered_dict_parametrized: typing.OrderedDict[int, str],
|
|
||||||
):
|
|
||||||
reveal_type(list_bare) # revealed: list
|
|
||||||
reveal_type(list_parametrized) # revealed: list
|
|
||||||
|
|
||||||
reveal_type(dict_bare) # revealed: dict
|
|
||||||
reveal_type(dict_parametrized) # revealed: dict
|
|
||||||
|
|
||||||
reveal_type(set_bare) # revealed: set
|
|
||||||
reveal_type(set_parametrized) # revealed: set
|
|
||||||
|
|
||||||
reveal_type(frozen_set_bare) # revealed: frozenset
|
|
||||||
reveal_type(frozen_set_parametrized) # revealed: frozenset
|
|
||||||
|
|
||||||
reveal_type(chain_map_bare) # revealed: ChainMap
|
|
||||||
reveal_type(chain_map_parametrized) # revealed: ChainMap
|
|
||||||
|
|
||||||
reveal_type(counter_bare) # revealed: Counter
|
|
||||||
reveal_type(counter_parametrized) # revealed: Counter
|
|
||||||
|
|
||||||
reveal_type(default_dict_bare) # revealed: defaultdict
|
|
||||||
reveal_type(default_dict_parametrized) # revealed: defaultdict
|
|
||||||
|
|
||||||
reveal_type(deque_bare) # revealed: deque
|
|
||||||
reveal_type(deque_parametrized) # revealed: deque
|
|
||||||
|
|
||||||
reveal_type(ordered_dict_bare) # revealed: OrderedDict
|
|
||||||
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inheritance
|
|
||||||
|
|
||||||
The aliases can be inherited from. Some of these are still partially or wholly TODOs.
|
|
||||||
|
|
||||||
```py
|
|
||||||
import typing
|
|
||||||
|
|
||||||
####################
|
|
||||||
### Built-ins
|
|
||||||
####################
|
|
||||||
|
|
||||||
class ListSubclass(typing.List): ...
|
|
||||||
|
|
||||||
# TODO: generic protocols
|
|
||||||
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
|
||||||
reveal_type(ListSubclass.__mro__)
|
|
||||||
|
|
||||||
class DictSubclass(typing.Dict): ...
|
|
||||||
|
|
||||||
# TODO: generic protocols
|
|
||||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
|
||||||
reveal_type(DictSubclass.__mro__)
|
|
||||||
|
|
||||||
class SetSubclass(typing.Set): ...
|
|
||||||
|
|
||||||
# TODO: generic protocols
|
|
||||||
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
|
||||||
reveal_type(SetSubclass.__mro__)
|
|
||||||
|
|
||||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
|
||||||
|
|
||||||
# TODO: should have `Generic`, should not have `Unknown`
|
|
||||||
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
|
|
||||||
reveal_type(FrozenSetSubclass.__mro__)
|
|
||||||
|
|
||||||
####################
|
|
||||||
### `collections`
|
|
||||||
####################
|
|
||||||
|
|
||||||
class ChainMapSubclass(typing.ChainMap): ...
|
|
||||||
|
|
||||||
# TODO: generic protocols
|
|
||||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
|
||||||
reveal_type(ChainMapSubclass.__mro__)
|
|
||||||
|
|
||||||
class CounterSubclass(typing.Counter): ...
|
|
||||||
|
|
||||||
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
|
||||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
|
|
||||||
reveal_type(CounterSubclass.__mro__)
|
|
||||||
|
|
||||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
|
||||||
|
|
||||||
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
|
||||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
|
|
||||||
reveal_type(DefaultDictSubclass.__mro__)
|
|
||||||
|
|
||||||
class DequeSubclass(typing.Deque): ...
|
|
||||||
|
|
||||||
# TODO: generic protocols
|
|
||||||
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
|
||||||
reveal_type(DequeSubclass.__mro__)
|
|
||||||
|
|
||||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
|
||||||
|
|
||||||
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
|
||||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
|
|
||||||
reveal_type(OrderedDictSubclass.__mro__)
|
|
||||||
```
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# No matching overload diagnostics
|
|
||||||
|
|
||||||
<!-- snapshot-diagnostics -->
|
|
||||||
|
|
||||||
## Calls to overloaded functions
|
|
||||||
|
|
||||||
TODO: Note that we do not yet support the `@overload` decorator to define overloaded functions in
|
|
||||||
real Python code. We are instead testing a special-cased function where we create an overloaded
|
|
||||||
signature internally. Update this to an `@overload` function in the Python snippet itself once we
|
|
||||||
can.
|
|
||||||
|
|
||||||
```py
|
|
||||||
type("Foo", ()) # error: [no-matching-overload]
|
|
||||||
```
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# `except*`
|
|
||||||
|
|
||||||
`except*` is only available in Python 3.11 and later:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[environment]
|
|
||||||
python-version = "3.11"
|
|
||||||
```
|
|
||||||
|
|
||||||
## `except*` with `BaseException`
|
|
||||||
|
|
||||||
```py
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* BaseException as e:
|
|
||||||
# TODO: should be `BaseExceptionGroup[BaseException]` --Alex
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
```
|
|
||||||
|
|
||||||
## `except*` with specific exception
|
|
||||||
|
|
||||||
```py
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* OSError as e:
|
|
||||||
# TODO: more precise would be `ExceptionGroup[OSError]` --Alex
|
|
||||||
# (needs homogeneous tuples + generics)
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
```
|
|
||||||
|
|
||||||
## `except*` with multiple exceptions
|
|
||||||
|
|
||||||
```py
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* (TypeError, AttributeError) as e:
|
|
||||||
# TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex
|
|
||||||
# (needs homogeneous tuples + generics)
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
```
|
|
||||||
|
|
||||||
## `except*` with mix of `Exception`s and `BaseException`s
|
|
||||||
|
|
||||||
```py
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* (KeyboardInterrupt, AttributeError) as e:
|
|
||||||
# TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invalid `except*` handlers
|
|
||||||
|
|
||||||
```py
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* 3 as e: # error: [invalid-exception-caught]
|
|
||||||
# TODO: Should be `BaseExceptionGroup[Unknown]` --Alex
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
|
|
||||||
try:
|
|
||||||
help()
|
|
||||||
except* (AttributeError, 42) as e: # error: [invalid-exception-caught]
|
|
||||||
# TODO: Should be `BaseExceptionGroup[AttributeError | Unknown]` --Alex
|
|
||||||
reveal_type(e) # revealed: BaseExceptionGroup
|
|
||||||
```
|
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
# Generic classes
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[environment]
|
|
||||||
python-version = "3.13"
|
|
||||||
```
|
|
||||||
|
|
||||||
## PEP 695 syntax
|
|
||||||
|
|
||||||
TODO: Add a `red_knot_extension` function that asserts whether a function or class is generic.
|
|
||||||
|
|
||||||
This is a generic class defined using PEP 695 syntax:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
A class that inherits from a generic class, and fills its type parameters with typevars, is generic:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class D[U](C[U]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
A class that inherits from a generic class, but fills its type parameters with concrete types, is
|
|
||||||
_not_ generic:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class E(C[int]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
A class that inherits from a generic class, and doesn't fill its type parameters at all, implicitly
|
|
||||||
uses the default value for the typevar. In this case, that default type is `Unknown`, so `F`
|
|
||||||
inherits from `C[Unknown]` and is not itself generic.
|
|
||||||
|
|
||||||
```py
|
|
||||||
class F(C): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Legacy syntax
|
|
||||||
|
|
||||||
This is a generic class defined using the legacy syntax:
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Generic, TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
class C(Generic[T]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
A class that inherits from a generic class, and fills its type parameters with typevars, is generic.
|
|
||||||
|
|
||||||
```py
|
|
||||||
class D(C[T]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
(Examples `E` and `F` from above do not have analogues in the legacy syntax.)
|
|
||||||
|
|
||||||
## Specializing generic classes explicitly
|
|
||||||
|
|
||||||
The type parameter can be specified explicitly:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
x: T
|
|
||||||
|
|
||||||
reveal_type(C[int]()) # revealed: C[int]
|
|
||||||
```
|
|
||||||
|
|
||||||
The specialization must match the generic types:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `C`: expected 1, got 2"
|
|
||||||
reveal_type(C[int, int]()) # revealed: Unknown
|
|
||||||
```
|
|
||||||
|
|
||||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Bounded[T: int]: ...
|
|
||||||
class BoundedByUnion[T: int | str]: ...
|
|
||||||
class IntSubclass(int): ...
|
|
||||||
|
|
||||||
reveal_type(Bounded[int]()) # revealed: Bounded[int]
|
|
||||||
reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass]
|
|
||||||
|
|
||||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `str`"
|
|
||||||
reveal_type(Bounded[str]()) # revealed: Unknown
|
|
||||||
|
|
||||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `int | str`"
|
|
||||||
reveal_type(Bounded[int | str]()) # revealed: Unknown
|
|
||||||
|
|
||||||
reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int]
|
|
||||||
reveal_type(BoundedByUnion[IntSubclass]()) # revealed: BoundedByUnion[IntSubclass]
|
|
||||||
reveal_type(BoundedByUnion[str]()) # revealed: BoundedByUnion[str]
|
|
||||||
reveal_type(BoundedByUnion[int | str]()) # revealed: BoundedByUnion[int | str]
|
|
||||||
```
|
|
||||||
|
|
||||||
If the type variable is constrained, the specialized type must satisfy those constraints:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Constrained[T: (int, str)]: ...
|
|
||||||
|
|
||||||
reveal_type(Constrained[int]()) # revealed: Constrained[int]
|
|
||||||
|
|
||||||
# TODO: error: [invalid-argument-type]
|
|
||||||
# TODO: revealed: Constrained[Unknown]
|
|
||||||
reveal_type(Constrained[IntSubclass]()) # revealed: Constrained[IntSubclass]
|
|
||||||
|
|
||||||
reveal_type(Constrained[str]()) # revealed: Constrained[str]
|
|
||||||
|
|
||||||
# TODO: error: [invalid-argument-type]
|
|
||||||
# TODO: revealed: Unknown
|
|
||||||
reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
|
|
||||||
|
|
||||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int | str`, found `object`"
|
|
||||||
reveal_type(Constrained[object]()) # revealed: Unknown
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inferring generic class parameters
|
|
||||||
|
|
||||||
We can infer the type parameter from a type context:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
x: T
|
|
||||||
|
|
||||||
c: C[int] = C()
|
|
||||||
# TODO: revealed: C[int]
|
|
||||||
reveal_type(c) # revealed: C[Unknown]
|
|
||||||
```
|
|
||||||
|
|
||||||
The typevars of a fully specialized generic class should no longer be visible:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# TODO: revealed: int
|
|
||||||
reveal_type(c.x) # revealed: Unknown
|
|
||||||
```
|
|
||||||
|
|
||||||
If the type parameter is not specified explicitly, and there are no constraints that let us infer a
|
|
||||||
specific type, we infer the typevar's default type:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class D[T = int]: ...
|
|
||||||
|
|
||||||
reveal_type(D()) # revealed: D[int]
|
|
||||||
```
|
|
||||||
|
|
||||||
If a typevar does not provide a default, we use `Unknown`:
|
|
||||||
|
|
||||||
```py
|
|
||||||
reveal_type(C()) # revealed: C[Unknown]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inferring generic class parameters from constructors
|
|
||||||
|
|
||||||
If the type of a constructor parameter is a class typevar, we can use that to infer the type
|
|
||||||
parameter. The types inferred from a type context and from a constructor parameter must be
|
|
||||||
consistent with each other.
|
|
||||||
|
|
||||||
## `__new__` only
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def __new__(cls, x: T) -> "C[T]":
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
|
||||||
wrong_innards: C[int] = C("five")
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__init__` only
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def __init__(self, x: T) -> None: ...
|
|
||||||
|
|
||||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
|
||||||
wrong_innards: C[int] = C("five")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Identical `__new__` and `__init__` signatures
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def __new__(cls, x: T) -> "C[T]":
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
def __init__(self, x: T) -> None: ...
|
|
||||||
|
|
||||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
|
||||||
wrong_innards: C[int] = C("five")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compatible `__new__` and `__init__` signatures
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def __new__(cls, *args, **kwargs) -> "C[T]":
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
def __init__(self, x: T) -> None: ...
|
|
||||||
|
|
||||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
|
||||||
wrong_innards: C[int] = C("five")
|
|
||||||
|
|
||||||
class D[T]:
|
|
||||||
def __new__(cls, x: T) -> "D[T]":
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None: ...
|
|
||||||
|
|
||||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`"
|
|
||||||
wrong_innards: D[int] = D("five")
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__init__` is itself generic
|
|
||||||
|
|
||||||
TODO: These do not currently work yet, because we don't correctly model the nested generic contexts.
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def __init__[S](self, x: T, y: S) -> None: ...
|
|
||||||
|
|
||||||
# TODO: no error
|
|
||||||
# TODO: revealed: C[Literal[1]]
|
|
||||||
# error: [invalid-argument-type]
|
|
||||||
reveal_type(C(1, 1)) # revealed: C[Unknown]
|
|
||||||
# TODO: no error
|
|
||||||
# TODO: revealed: C[Literal[1]]
|
|
||||||
# error: [invalid-argument-type]
|
|
||||||
reveal_type(C(1, "string")) # revealed: C[Unknown]
|
|
||||||
# TODO: no error
|
|
||||||
# TODO: revealed: C[Literal[1]]
|
|
||||||
# error: [invalid-argument-type]
|
|
||||||
reveal_type(C(1, True)) # revealed: C[Unknown]
|
|
||||||
|
|
||||||
# TODO: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
|
||||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `S`, found `Literal[1]`"
|
|
||||||
wrong_innards: C[int] = C("five", 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generic subclass
|
|
||||||
|
|
||||||
When a generic subclass fills its superclass's type parameter with one of its own, the actual types
|
|
||||||
propagate through:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Base[T]:
|
|
||||||
x: T | None = None
|
|
||||||
|
|
||||||
class Sub[U](Base[U]): ...
|
|
||||||
|
|
||||||
reveal_type(Base[int].x) # revealed: int | None
|
|
||||||
reveal_type(Sub[int].x) # revealed: int | None
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generic methods
|
|
||||||
|
|
||||||
Generic classes can contain methods that are themselves generic. The generic methods can refer to
|
|
||||||
the typevars of the enclosing generic class, and introduce new (distinct) typevars that are only in
|
|
||||||
scope for the method.
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C[T]:
|
|
||||||
def method[U](self, u: U) -> U:
|
|
||||||
return u
|
|
||||||
# error: [unresolved-reference]
|
|
||||||
def cannot_use_outside_of_method(self, u: U): ...
|
|
||||||
|
|
||||||
# TODO: error
|
|
||||||
def cannot_shadow_class_typevar[T](self, t: T): ...
|
|
||||||
|
|
||||||
c: C[int] = C[int]()
|
|
||||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cyclic class definition
|
|
||||||
|
|
||||||
A class can use itself as the type parameter of one of its superclasses. (This is also known as the
|
|
||||||
[curiously recurring template pattern][crtp] or [F-bounded quantification][f-bound].)
|
|
||||||
|
|
||||||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
|
||||||
|
|
||||||
`stub.pyi`:
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
class Base[T]: ...
|
|
||||||
class Sub(Base[Sub]): ...
|
|
||||||
|
|
||||||
reveal_type(Sub) # revealed: Literal[Sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
A similar case can work in a non-stub file, if forward references are stringified:
|
|
||||||
|
|
||||||
`string_annotation.py`:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Base[T]: ...
|
|
||||||
class Sub(Base["Sub"]): ...
|
|
||||||
|
|
||||||
reveal_type(Sub) # revealed: Literal[Sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
|
||||||
|
|
||||||
`bare_annotation.py`:
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Base[T]: ...
|
|
||||||
|
|
||||||
# error: [unresolved-reference]
|
|
||||||
class Sub(Base[Sub]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Another cyclic case
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
# TODO no error (generics)
|
|
||||||
# error: [invalid-base]
|
|
||||||
class Derived[T](list[Derived[T]]): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
|
||||||
[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# Legacy type variables
|
|
||||||
|
|
||||||
The tests in this file focus on how type variables are defined using the legacy notation. Most
|
|
||||||
_uses_ of type variables are tested in other files in this directory; we do not duplicate every test
|
|
||||||
for both type variable syntaxes.
|
|
||||||
|
|
||||||
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
|
|
||||||
|
|
||||||
## Type variables
|
|
||||||
|
|
||||||
### Defining legacy type variables
|
|
||||||
|
|
||||||
> Generics can be parameterized by using a factory available in `typing` called `TypeVar`.
|
|
||||||
|
|
||||||
This was the only way to create type variables prior to PEP 695/Python 3.12. It is still available
|
|
||||||
in newer Python releases.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Directly assigned to a variable
|
|
||||||
|
|
||||||
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
|
|
||||||
> part of a larger expression).
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
# TODO: error
|
|
||||||
TestList = list[TypeVar("W")]
|
|
||||||
```
|
|
||||||
|
|
||||||
### `TypeVar` parameter must match variable name
|
|
||||||
|
|
||||||
> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
# TODO: error
|
|
||||||
T = TypeVar("Q")
|
|
||||||
```
|
|
||||||
|
|
||||||
### No redefinition
|
|
||||||
|
|
||||||
> Type variables must not be redefined.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
# TODO: error
|
|
||||||
T = TypeVar("T")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cannot have only one constraint
|
|
||||||
|
|
||||||
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
|
|
||||||
> be at least two constraints, if any; specifying a single constraint is disallowed.
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
# TODO: error: [invalid-type-variable-constraints]
|
|
||||||
T = TypeVar("T", int)
|
|
||||||
```
|
|
||||||
|
|
||||||
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Dictionaries
|
|
||||||
|
|
||||||
## Empty dictionary
|
|
||||||
|
|
||||||
```py
|
|
||||||
reveal_type({}) # revealed: dict
|
|
||||||
```
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Lists
|
|
||||||
|
|
||||||
## Empty list
|
|
||||||
|
|
||||||
```py
|
|
||||||
reveal_type([]) # revealed: list
|
|
||||||
```
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Sets
|
|
||||||
|
|
||||||
## Basic set
|
|
||||||
|
|
||||||
```py
|
|
||||||
reveal_type({1, 2}) # revealed: set
|
|
||||||
```
|
|
||||||
@@ -1,408 +0,0 @@
|
|||||||
# Method Resolution Order tests
|
|
||||||
|
|
||||||
Tests that assert that we can infer the correct type for a class's `__mro__` attribute.
|
|
||||||
|
|
||||||
This attribute is rarely accessed directly at runtime. However, it's extremely important for *us* to
|
|
||||||
know the precise possible values of a class's Method Resolution Order, or we won't be able to infer
|
|
||||||
the correct type of attributes accessed from instances.
|
|
||||||
|
|
||||||
For documentation on method resolution orders, see:
|
|
||||||
|
|
||||||
- <https://docs.python.org/3/glossary.html#term-method-resolution-order>
|
|
||||||
- <https://docs.python.org/3/howto/mro.html#python-2-3-mro>
|
|
||||||
|
|
||||||
## No bases
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C: ...
|
|
||||||
|
|
||||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## The special case: `object` itself
|
|
||||||
|
|
||||||
```py
|
|
||||||
reveal_type(object.__mro__) # revealed: tuple[Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Explicit inheritance from `object`
|
|
||||||
|
|
||||||
```py
|
|
||||||
class C(object): ...
|
|
||||||
|
|
||||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Explicit inheritance from non-`object` single base
|
|
||||||
|
|
||||||
```py
|
|
||||||
class A: ...
|
|
||||||
class B(A): ...
|
|
||||||
|
|
||||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[A], Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Linearization of multiple bases
|
|
||||||
|
|
||||||
```py
|
|
||||||
class A: ...
|
|
||||||
class B: ...
|
|
||||||
class C(A, B): ...
|
|
||||||
|
|
||||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[A], Literal[B], Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complex diamond inheritance (1)
|
|
||||||
|
|
||||||
This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
|
|
||||||
|
|
||||||
```py
|
|
||||||
class O: ...
|
|
||||||
class X(O): ...
|
|
||||||
class Y(O): ...
|
|
||||||
class A(X, Y): ...
|
|
||||||
class B(Y, X): ...
|
|
||||||
|
|
||||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
|
||||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complex diamond inheritance (2)
|
|
||||||
|
|
||||||
This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
|
|
||||||
|
|
||||||
```py
|
|
||||||
class O: ...
|
|
||||||
class F(O): ...
|
|
||||||
class E(O): ...
|
|
||||||
class D(O): ...
|
|
||||||
class C(D, F): ...
|
|
||||||
class B(D, E): ...
|
|
||||||
class A(B, C): ...
|
|
||||||
|
|
||||||
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
|
||||||
reveal_type(C.__mro__)
|
|
||||||
# revealed: tuple[Literal[B], Literal[D], Literal[E], Literal[O], Literal[object]]
|
|
||||||
reveal_type(B.__mro__)
|
|
||||||
# revealed: tuple[Literal[A], Literal[B], Literal[C], Literal[D], Literal[E], Literal[F], Literal[O], Literal[object]]
|
|
||||||
reveal_type(A.__mro__)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complex diamond inheritance (3)
|
|
||||||
|
|
||||||
This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
|
|
||||||
|
|
||||||
```py
|
|
||||||
class O: ...
|
|
||||||
class F(O): ...
|
|
||||||
class E(O): ...
|
|
||||||
class D(O): ...
|
|
||||||
class C(D, F): ...
|
|
||||||
class B(E, D): ...
|
|
||||||
class A(B, C): ...
|
|
||||||
|
|
||||||
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
|
||||||
reveal_type(C.__mro__)
|
|
||||||
# revealed: tuple[Literal[B], Literal[E], Literal[D], Literal[O], Literal[object]]
|
|
||||||
reveal_type(B.__mro__)
|
|
||||||
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
|
||||||
reveal_type(A.__mro__)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complex diamond inheritance (4)
|
|
||||||
|
|
||||||
This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
|
|
||||||
|
|
||||||
```py
|
|
||||||
class O: ...
|
|
||||||
class A(O): ...
|
|
||||||
class B(O): ...
|
|
||||||
class C(O): ...
|
|
||||||
class D(O): ...
|
|
||||||
class E(O): ...
|
|
||||||
class K1(A, B, C): ...
|
|
||||||
class K2(D, B, E): ...
|
|
||||||
class K3(D, A): ...
|
|
||||||
class Z(K1, K2, K3): ...
|
|
||||||
|
|
||||||
# revealed: tuple[Literal[K1], Literal[A], Literal[B], Literal[C], Literal[O], Literal[object]]
|
|
||||||
reveal_type(K1.__mro__)
|
|
||||||
# revealed: tuple[Literal[K2], Literal[D], Literal[B], Literal[E], Literal[O], Literal[object]]
|
|
||||||
reveal_type(K2.__mro__)
|
|
||||||
# revealed: tuple[Literal[K3], Literal[D], Literal[A], Literal[O], Literal[object]]
|
|
||||||
reveal_type(K3.__mro__)
|
|
||||||
# revealed: tuple[Literal[Z], Literal[K1], Literal[K2], Literal[K3], Literal[D], Literal[A], Literal[B], Literal[C], Literal[E], Literal[O], Literal[object]]
|
|
||||||
reveal_type(Z.__mro__)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inheritance from `Unknown`
|
|
||||||
|
|
||||||
```py
|
|
||||||
from does_not_exist import DoesNotExist # error: [unresolved-import]
|
|
||||||
|
|
||||||
class A(DoesNotExist): ...
|
|
||||||
class B: ...
|
|
||||||
class C: ...
|
|
||||||
class D(A, B, C): ...
|
|
||||||
class E(B, C): ...
|
|
||||||
class F(E, A): ...
|
|
||||||
|
|
||||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
|
|
||||||
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[A], Unknown, Literal[B], Literal[C], Literal[object]]
|
|
||||||
reveal_type(E.__mro__) # revealed: tuple[Literal[E], Literal[B], Literal[C], Literal[object]]
|
|
||||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], Literal[E], Literal[B], Literal[C], Literal[A], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` lists that cause errors at runtime
|
|
||||||
|
|
||||||
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class
|
|
||||||
creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
|
|
||||||
class Foo(object, int): ...
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class Bar(Foo): ...
|
|
||||||
|
|
||||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Literal[Foo], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
# This is the `TypeError` at the bottom of "ex_2"
|
|
||||||
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
|
|
||||||
class O: ...
|
|
||||||
class X(O): ...
|
|
||||||
class Y(O): ...
|
|
||||||
class A(X, Y): ...
|
|
||||||
class B(Y, X): ...
|
|
||||||
|
|
||||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
|
||||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
|
||||||
|
|
||||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
|
|
||||||
class Z(A, B): ...
|
|
||||||
|
|
||||||
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class AA(Z): ...
|
|
||||||
|
|
||||||
reveal_type(AA.__mro__) # revealed: tuple[Literal[AA], Literal[Z], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` includes a `Union`
|
|
||||||
|
|
||||||
We don't support union types in a class's bases; a base must resolve to a single `ClassLiteralType`.
|
|
||||||
If we find a union type in a class's bases, we infer the class's `__mro__` as being
|
|
||||||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
|
||||||
|
|
||||||
```py
|
|
||||||
def returns_bool() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
class A: ...
|
|
||||||
class B: ...
|
|
||||||
|
|
||||||
if returns_bool():
|
|
||||||
x = A
|
|
||||||
else:
|
|
||||||
x = B
|
|
||||||
|
|
||||||
reveal_type(x) # revealed: Literal[A, B]
|
|
||||||
|
|
||||||
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
|
||||||
class Foo(x): ...
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` includes multiple `Union`s
|
|
||||||
|
|
||||||
```py
|
|
||||||
def returns_bool() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
class A: ...
|
|
||||||
class B: ...
|
|
||||||
class C: ...
|
|
||||||
class D: ...
|
|
||||||
|
|
||||||
if returns_bool():
|
|
||||||
x = A
|
|
||||||
else:
|
|
||||||
x = B
|
|
||||||
|
|
||||||
if returns_bool():
|
|
||||||
y = C
|
|
||||||
else:
|
|
||||||
y = D
|
|
||||||
|
|
||||||
reveal_type(x) # revealed: Literal[A, B]
|
|
||||||
reveal_type(y) # revealed: Literal[C, D]
|
|
||||||
|
|
||||||
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
|
||||||
# error: 14 [invalid-base] "Invalid class base with type `Literal[C, D]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
|
||||||
class Foo(x, y): ...
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` lists that cause errors... now with `Union`s
|
|
||||||
|
|
||||||
```py
|
|
||||||
def returns_bool() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
class O: ...
|
|
||||||
class X(O): ...
|
|
||||||
class Y(O): ...
|
|
||||||
|
|
||||||
if returns_bool():
|
|
||||||
foo = Y
|
|
||||||
else:
|
|
||||||
foo = object
|
|
||||||
|
|
||||||
# error: 21 [invalid-base] "Invalid class base with type `Literal[Y, object]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
|
||||||
class PossibleError(foo, X): ...
|
|
||||||
|
|
||||||
reveal_type(PossibleError.__mro__) # revealed: tuple[Literal[PossibleError], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class A(X, Y): ...
|
|
||||||
|
|
||||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
|
||||||
|
|
||||||
if returns_bool():
|
|
||||||
class B(X, Y): ...
|
|
||||||
|
|
||||||
else:
|
|
||||||
class B(Y, X): ...
|
|
||||||
|
|
||||||
# revealed: tuple[Literal[B], Literal[X], Literal[Y], Literal[O], Literal[object]] | tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
|
||||||
reveal_type(B.__mro__)
|
|
||||||
|
|
||||||
# error: 12 [invalid-base] "Invalid class base with type `Literal[B, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
|
||||||
class Z(A, B): ...
|
|
||||||
|
|
||||||
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` lists with duplicate bases
|
|
||||||
|
|
||||||
```py
|
|
||||||
class Foo(str, str): ... # error: 16 [duplicate-base] "Duplicate base class `str`"
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class Spam: ...
|
|
||||||
class Eggs: ...
|
|
||||||
class Ham(
|
|
||||||
Spam,
|
|
||||||
Eggs,
|
|
||||||
Spam, # error: [duplicate-base] "Duplicate base class `Spam`"
|
|
||||||
Eggs, # error: [duplicate-base] "Duplicate base class `Eggs`"
|
|
||||||
): ...
|
|
||||||
|
|
||||||
reveal_type(Ham.__mro__) # revealed: tuple[Literal[Ham], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class Mushrooms: ...
|
|
||||||
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
|
||||||
|
|
||||||
reveal_type(Omelette.__mro__) # revealed: tuple[Literal[Omelette], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `__bases__` lists with duplicate `Unknown` bases
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unresolved-import]
|
|
||||||
# error: [unresolved-import]
|
|
||||||
from does_not_exist import unknown_object_1, unknown_object_2
|
|
||||||
|
|
||||||
reveal_type(unknown_object_1) # revealed: Unknown
|
|
||||||
reveal_type(unknown_object_2) # revealed: Unknown
|
|
||||||
|
|
||||||
# We *should* emit an error here to warn the user that we have no idea
|
|
||||||
# what the MRO of this class should really be.
|
|
||||||
# However, we don't complain about "duplicate base classes" here,
|
|
||||||
# even though two classes are both inferred as being `Unknown`.
|
|
||||||
#
|
|
||||||
# (TODO: should we revisit this? Does it violate the gradual guarantee?
|
|
||||||
# Should we just silently infer `[Foo, Unknown, object]` as the MRO here
|
|
||||||
# without emitting any error at all? Not sure...)
|
|
||||||
#
|
|
||||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[Unknown, Unknown]`"
|
|
||||||
class Foo(unknown_object_1, unknown_object_2): ...
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
|
|
||||||
|
|
||||||
```py
|
|
||||||
from does_not_exist import unknown_object # error: [unresolved-import]
|
|
||||||
|
|
||||||
reveal_type(unknown_object) # revealed: Unknown
|
|
||||||
reveal_type(unknown_object.__mro__) # revealed: Unknown
|
|
||||||
```
|
|
||||||
|
|
||||||
## Classes that inherit from themselves
|
|
||||||
|
|
||||||
These are invalid, but we need to be able to handle them gracefully without panicking.
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
class Foo(Foo): ... # error: [cyclic-class-definition]
|
|
||||||
|
|
||||||
reveal_type(Foo) # revealed: Literal[Foo]
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
|
|
||||||
class Bar: ...
|
|
||||||
class Baz: ...
|
|
||||||
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
|
|
||||||
|
|
||||||
reveal_type(Boz) # revealed: Literal[Boz]
|
|
||||||
reveal_type(Boz.__mro__) # revealed: tuple[Literal[Boz], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Classes with indirect cycles in their MROs
|
|
||||||
|
|
||||||
These are similarly unlikely, but we still shouldn't crash:
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
|
||||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
|
||||||
class Baz(Foo): ... # error: [cyclic-class-definition]
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
|
|
||||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Classes with cycles in their MROs, and multiple inheritance
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
class Spam: ...
|
|
||||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
|
||||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
|
||||||
class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
|
|
||||||
|
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
|
||||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
|
|
||||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Classes with cycles in their MRO, and a sub-graph
|
|
||||||
|
|
||||||
```pyi
|
|
||||||
class FooCycle(BarCycle): ... # error: [cyclic-class-definition]
|
|
||||||
class Foo: ...
|
|
||||||
class BarCycle(FooCycle): ... # error: [cyclic-class-definition]
|
|
||||||
class Bar(Foo): ...
|
|
||||||
|
|
||||||
# Avoid emitting the errors for these. The classes have cyclic superclasses,
|
|
||||||
# but are not themselves cyclic...
|
|
||||||
class Baz(Bar, BarCycle): ...
|
|
||||||
class Spam(Baz): ...
|
|
||||||
|
|
||||||
reveal_type(FooCycle.__mro__) # revealed: tuple[Literal[FooCycle], Unknown, Literal[object]]
|
|
||||||
reveal_type(BarCycle.__mro__) # revealed: tuple[Literal[BarCycle], Unknown, Literal[object]]
|
|
||||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
|
||||||
reveal_type(Spam.__mro__) # revealed: tuple[Literal[Spam], Unknown, Literal[object]]
|
|
||||||
```
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Narrowing with assert statements
|
|
||||||
|
|
||||||
## `assert` a value `is None` or `is not None`
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(x: str | None, y: str | None):
|
|
||||||
assert x is not None
|
|
||||||
reveal_type(x) # revealed: str
|
|
||||||
assert y is None
|
|
||||||
reveal_type(y) # revealed: None
|
|
||||||
```
|
|
||||||
|
|
||||||
## `assert` a value is truthy or falsy
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(x: bool, y: bool):
|
|
||||||
assert x
|
|
||||||
reveal_type(x) # revealed: Literal[True]
|
|
||||||
assert not y
|
|
||||||
reveal_type(y) # revealed: Literal[False]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `assert` with `is` and `==` for literals
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
def _(x: Literal[1, 2, 3], y: Literal[1, 2, 3]):
|
|
||||||
assert x is 2
|
|
||||||
reveal_type(x) # revealed: Literal[2]
|
|
||||||
assert y == 2
|
|
||||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
|
||||||
```
|
|
||||||
|
|
||||||
## `assert` with `isinstance`
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(x: int | str):
|
|
||||||
assert isinstance(x, int)
|
|
||||||
reveal_type(x) # revealed: int
|
|
||||||
```
|
|
||||||
|
|
||||||
## `assert` a value `in` a tuple
|
|
||||||
|
|
||||||
```py
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
def _(x: Literal[1, 2, 3], y: Literal[1, 2, 3]):
|
|
||||||
assert x in (1, 2)
|
|
||||||
reveal_type(x) # revealed: Literal[1, 2]
|
|
||||||
assert y not in (1, 2)
|
|
||||||
reveal_type(y) # revealed: Literal[3]
|
|
||||||
```
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# Narrowing for nested conditionals
|
|
||||||
|
|
||||||
## Multiple negative contributions
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(x: int):
|
|
||||||
if x != 1:
|
|
||||||
if x != 2:
|
|
||||||
if x != 3:
|
|
||||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multiple negative contributions with simplification
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(flag1: bool, flag2: bool):
|
|
||||||
x = 1 if flag1 else 2 if flag2 else 3
|
|
||||||
|
|
||||||
if x != 1:
|
|
||||||
reveal_type(x) # revealed: Literal[2, 3]
|
|
||||||
if x != 2:
|
|
||||||
reveal_type(x) # revealed: Literal[3]
|
|
||||||
```
|
|
||||||
|
|
||||||
## elif-else blocks
|
|
||||||
|
|
||||||
```py
|
|
||||||
def _(flag1: bool, flag2: bool):
|
|
||||||
x = 1 if flag1 else 2 if flag2 else 3
|
|
||||||
|
|
||||||
if x != 1:
|
|
||||||
reveal_type(x) # revealed: Literal[2, 3]
|
|
||||||
if x == 2:
|
|
||||||
# TODO should be `Literal[2]`
|
|
||||||
reveal_type(x) # revealed: Literal[2, 3]
|
|
||||||
elif x == 3:
|
|
||||||
reveal_type(x) # revealed: Literal[3]
|
|
||||||
else:
|
|
||||||
reveal_type(x) # revealed: Never
|
|
||||||
|
|
||||||
elif x != 2:
|
|
||||||
# TODO should be Literal[1]
|
|
||||||
reveal_type(x) # revealed: Literal[1, 3]
|
|
||||||
else:
|
|
||||||
# TODO should be Never
|
|
||||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: basic.md - Structures - Unresolvable module import
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/import/basic.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve import `zqzqzqzqzqzqzq`"
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:unresolved-import
|
|
||||||
--> /src/mdtest_snippet.py:1:8
|
|
||||||
|
|
|
||||||
1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve import `zqzqzqzqzqzqzq`"
|
|
||||||
| ^^^^^^^^^^^^^^ Cannot resolve import `zqzqzqzqzqzqzq`
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: basic.md - Structures - Unresolvable submodule imports
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/import/basic.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | # Topmost component resolvable, submodule not resolvable:
|
|
||||||
2 | import a.foo # error: [unresolved-import] "Cannot resolve import `a.foo`"
|
|
||||||
3 |
|
|
||||||
4 | # Topmost component unresolvable:
|
|
||||||
5 | import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
|
|
||||||
```
|
|
||||||
|
|
||||||
## a/__init__.py
|
|
||||||
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:unresolved-import
|
|
||||||
--> /src/mdtest_snippet.py:2:8
|
|
||||||
|
|
|
||||||
1 | # Topmost component resolvable, submodule not resolvable:
|
|
||||||
2 | import a.foo # error: [unresolved-import] "Cannot resolve import `a.foo`"
|
|
||||||
| ^^^^^ Cannot resolve import `a.foo`
|
|
||||||
3 |
|
|
||||||
4 | # Topmost component unresolvable:
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:unresolved-import
|
|
||||||
--> /src/mdtest_snippet.py:5:8
|
|
||||||
|
|
|
||||||
4 | # Topmost component unresolvable:
|
|
||||||
5 | import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
|
|
||||||
| ^^^^^ Cannot resolve import `b.foo`
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: for.md - For loops - Invalid iterable
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | nonsense = 123
|
|
||||||
2 | for x in nonsense: # error: [not-iterable]
|
|
||||||
3 | pass
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:not-iterable
|
|
||||||
--> /src/mdtest_snippet.py:2:10
|
|
||||||
|
|
|
||||||
1 | nonsense = 123
|
|
||||||
2 | for x in nonsense: # error: [not-iterable]
|
|
||||||
| ^^^^^^^^ Object of type `Literal[123]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
|
||||||
3 | pass
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: no_matching_overload.md - No matching overload diagnostics - Calls to overloaded functions
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | type("Foo", ()) # error: [no-matching-overload]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:no-matching-overload
|
|
||||||
--> /src/mdtest_snippet.py:1:1
|
|
||||||
|
|
|
||||||
1 | type("Foo", ()) # error: [no-matching-overload]
|
|
||||||
| ^^^^^^^^^^^^^^^ No overload of class `type` matches arguments
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: return_type.md - Function return type - Invalid return type
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_type.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | # error: [invalid-return-type]
|
|
||||||
2 | def f() -> int:
|
|
||||||
3 | 1
|
|
||||||
4 |
|
|
||||||
5 | def f() -> str:
|
|
||||||
6 | # error: [invalid-return-type]
|
|
||||||
7 | return 1
|
|
||||||
8 |
|
|
||||||
9 | def f() -> int:
|
|
||||||
10 | # error: [invalid-return-type]
|
|
||||||
11 | return
|
|
||||||
12 |
|
|
||||||
13 | from typing import TypeVar
|
|
||||||
14 |
|
|
||||||
15 | T = TypeVar("T")
|
|
||||||
16 |
|
|
||||||
17 | # TODO: `invalid-return-type` error should be emitted
|
|
||||||
18 | def m(x: T) -> T: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:invalid-return-type
|
|
||||||
--> /src/mdtest_snippet.py:2:12
|
|
||||||
|
|
|
||||||
1 | # error: [invalid-return-type]
|
|
||||||
2 | def f() -> int:
|
|
||||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
|
||||||
3 | 1
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:invalid-return-type: Return type does not match returned value
|
|
||||||
--> /src/mdtest_snippet.py:5:12
|
|
||||||
|
|
|
||||||
3 | 1
|
|
||||||
4 |
|
|
||||||
5 | def f() -> str:
|
|
||||||
| --- Expected `str` because of return type
|
|
||||||
6 | # error: [invalid-return-type]
|
|
||||||
7 | return 1
|
|
||||||
| ^ Expected `str`, found `Literal[1]`
|
|
||||||
8 |
|
|
||||||
9 | def f() -> int:
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:invalid-return-type: Return type does not match returned value
|
|
||||||
--> /src/mdtest_snippet.py:9:12
|
|
||||||
|
|
|
||||||
7 | return 1
|
|
||||||
8 |
|
|
||||||
9 | def f() -> int:
|
|
||||||
| --- Expected `int` because of return type
|
|
||||||
10 | # error: [invalid-return-type]
|
|
||||||
11 | return
|
|
||||||
| ^^^^^^ Expected `int`, found `None`
|
|
||||||
12 |
|
|
||||||
13 | from typing import TypeVar
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: unpacking.md - Unpacking - Right hand side not iterable
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpacking.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | a, b = 1 # error: [not-iterable]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:not-iterable
|
|
||||||
--> /src/mdtest_snippet.py:1:8
|
|
||||||
|
|
|
||||||
1 | a, b = 1 # error: [not-iterable]
|
|
||||||
| ^ Object of type `Literal[1]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: unpacking.md - Unpacking - Too few values to unpack
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpacking.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | a, b = (1,) # error: [invalid-assignment]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:invalid-assignment
|
|
||||||
--> /src/mdtest_snippet.py:1:1
|
|
||||||
|
|
|
||||||
1 | a, b = (1,) # error: [invalid-assignment]
|
|
||||||
| ^^^^ Not enough values to unpack (expected 2, got 1)
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: unpacking.md - Unpacking - Too many values to unpack
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpacking.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | a, b = (1, 2, 3) # error: [invalid-assignment]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:invalid-assignment
|
|
||||||
--> /src/mdtest_snippet.py:1:1
|
|
||||||
|
|
|
||||||
1 | a, b = (1, 2, 3) # error: [invalid-assignment]
|
|
||||||
| ^^^^ Too many values to unpack (expected 2, got 3)
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: unresolved_import.md - Unresolved import diagnostics - An unresolvable import that does not use `from`
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unresolved_import.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | import does_not_exist # error: [unresolved-import]
|
|
||||||
2 |
|
|
||||||
3 | x = does_not_exist.foo
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:unresolved-import
|
|
||||||
--> /src/mdtest_snippet.py:1:8
|
|
||||||
|
|
|
||||||
1 | import does_not_exist # error: [unresolved-import]
|
|
||||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
|
||||||
2 |
|
|
||||||
3 | x = does_not_exist.foo
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
source: crates/red_knot_test/src/lib.rs
|
|
||||||
expression: snapshot
|
|
||||||
---
|
|
||||||
---
|
|
||||||
mdtest name: unresolved_import.md - Unresolved import diagnostics - Using `from` with an unresolvable module
|
|
||||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unresolved_import.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Python source files
|
|
||||||
|
|
||||||
## mdtest_snippet.py
|
|
||||||
|
|
||||||
```
|
|
||||||
1 | from does_not_exist import add # error: [unresolved-import]
|
|
||||||
2 |
|
|
||||||
3 | stat = add(10, 15)
|
|
||||||
```
|
|
||||||
|
|
||||||
# Diagnostics
|
|
||||||
|
|
||||||
```
|
|
||||||
error: lint:unresolved-import
|
|
||||||
--> /src/mdtest_snippet.py:1:6
|
|
||||||
|
|
|
||||||
1 | from does_not_exist import add # error: [unresolved-import]
|
|
||||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
|
||||||
2 |
|
|
||||||
3 | stat = add(10, 15)
|
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
# Suppressing errors with `knot: ignore`
|
|
||||||
|
|
||||||
Type check errors can be suppressed by a `knot: ignore` comment on the same line as the violation.
|
|
||||||
|
|
||||||
## Simple `knot: ignore`
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 4 + test # knot: ignore
|
|
||||||
```
|
|
||||||
|
|
||||||
## Suppressing a specific code
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 4 + test # knot: ignore[unresolved-reference]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unused suppression
|
|
||||||
|
|
||||||
```py
|
|
||||||
test = 10
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
|
||||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unused suppression if the error codes don't match
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unresolved-reference]
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
|
||||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Suppressed unused comment
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unused-ignore-comment]
|
|
||||||
a = 10 / 2 # knot: ignore[division-by-zero]
|
|
||||||
a = 10 / 2 # knot: ignore[division-by-zero, unused-ignore-comment]
|
|
||||||
a = 10 / 2 # knot: ignore[unused-ignore-comment, division-by-zero]
|
|
||||||
a = 10 / 2 # knot: ignore[unused-ignore-comment] # type: ignore
|
|
||||||
a = 10 / 2 # type: ignore # knot: ignore[unused-ignore-comment]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unused ignore comment
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unused-ignore-comment'"
|
|
||||||
a = 10 / 0 # knot: ignore[division-by-zero, unused-ignore-comment]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multiple unused comments
|
|
||||||
|
|
||||||
Today, Red Knot emits a diagnostic for every unused code. We might want to group the codes by
|
|
||||||
comment at some point in the future.
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'division-by-zero'"
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
|
||||||
a = 10 / 2 # knot: ignore[division-by-zero, unresolved-reference]
|
|
||||||
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'invalid-assignment'"
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
|
||||||
a = 10 / 0 # knot: ignore[invalid-assignment, division-by-zero, unresolved-reference]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multiple suppressions
|
|
||||||
|
|
||||||
```py
|
|
||||||
# fmt: off
|
|
||||||
def test(a: f"f-string type annotation", b: b"byte-string-type-annotation"): ... # knot: ignore[fstring-type-annotation, byte-string-type-annotation]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Can't suppress syntax errors
|
|
||||||
|
|
||||||
<!-- blacken-docs:off -->
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [invalid-syntax]
|
|
||||||
# error: [unused-ignore-comment]
|
|
||||||
def test($): # knot: ignore
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- blacken-docs:on -->
|
|
||||||
|
|
||||||
## Can't suppress `revealed-type` diagnostics
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 10
|
|
||||||
# revealed: Literal[10]
|
|
||||||
# error: [unknown-rule] "Unknown rule `revealed-type`"
|
|
||||||
reveal_type(a) # knot: ignore[revealed-type]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Extra whitespace in type ignore comments is allowed
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 10 / 0 # knot : ignore
|
|
||||||
a = 10 / 0 # knot: ignore [ division-by-zero ]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Whitespace is optional
|
|
||||||
|
|
||||||
```py
|
|
||||||
# fmt: off
|
|
||||||
a = 10 / 0 #knot:ignore[division-by-zero]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Trailing codes comma
|
|
||||||
|
|
||||||
Trailing commas in the codes section are allowed:
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 10 / 0 # knot: ignore[division-by-zero,]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invalid characters in codes
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [division-by-zero]
|
|
||||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a alphanumeric character or `-` or `_` as code"
|
|
||||||
a = 10 / 0 # knot: ignore[*-*]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Trailing whitespace
|
|
||||||
|
|
||||||
<!-- blacken-docs:off -->
|
|
||||||
|
|
||||||
```py
|
|
||||||
a = 10 / 0 # knot: ignore[division-by-zero]
|
|
||||||
# ^^^^^^ trailing whitespace
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- blacken-docs:on -->
|
|
||||||
|
|
||||||
## Missing comma
|
|
||||||
|
|
||||||
A missing comma results in an invalid suppression comment. We may want to recover from this in the
|
|
||||||
future.
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unresolved-reference]
|
|
||||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
|
||||||
a = x / 0 # knot: ignore[division-by-zero unresolved-reference]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Missing closing bracket
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
|
||||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
|
||||||
a = x / 2 # knot: ignore[unresolved-reference
|
|
||||||
```
|
|
||||||
|
|
||||||
## Empty codes
|
|
||||||
|
|
||||||
An empty codes array suppresses no-diagnostics and is always useless
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [division-by-zero]
|
|
||||||
# error: [unused-ignore-comment] "Unused `knot: ignore` without a code"
|
|
||||||
a = 4 / 0 # knot: ignore[]
|
|
||||||
```
|
|
||||||
|
|
||||||
## File-level suppression comments
|
|
||||||
|
|
||||||
File level suppression comments are currently intentionally unsupported because we've yet to decide
|
|
||||||
if they should use a different syntax that also supports enabling rules or changing the rule's
|
|
||||||
severity: `knot: possibly-undefined-reference=error`
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unused-ignore-comment]
|
|
||||||
# knot: ignore[division-by-zero]
|
|
||||||
|
|
||||||
a = 4 / 0 # error: [division-by-zero]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unknown rule
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error: [unknown-rule] "Unknown rule `is-equal-14`"
|
|
||||||
a = 10 + 4 # knot: ignore[is-equal-14]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code with `lint:` prefix
|
|
||||||
|
|
||||||
```py
|
|
||||||
# error:[unknown-rule] "Unknown rule `lint:division-by-zero`. Did you mean `division-by-zero`?"
|
|
||||||
# error: [division-by-zero]
|
|
||||||
a = 10 / 0 # knot: ignore[lint:division-by-zero]
|
|
||||||
```
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
arrow
|
|
||||||
async-utils
|
|
||||||
bidict
|
|
||||||
black
|
|
||||||
dacite
|
|
||||||
git-revise
|
|
||||||
isort
|
|
||||||
itsdangerous
|
|
||||||
mypy_primer
|
|
||||||
packaging
|
|
||||||
paroxython
|
|
||||||
porcupine
|
|
||||||
psycopg
|
|
||||||
pybind11
|
|
||||||
pyinstrument
|
|
||||||
pyp
|
|
||||||
python-chess
|
|
||||||
python-htmlgen
|
|
||||||
rich
|
|
||||||
scrapy
|
|
||||||
typeshed-stats
|
|
||||||
werkzeug
|
|
||||||
zipp
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,407 +0,0 @@
|
|||||||
use ruff_python_ast as ast;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use crate::semantic_index::SemanticIndex;
|
|
||||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
|
||||||
use crate::types::{
|
|
||||||
declaration_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance,
|
|
||||||
UnionBuilder, UnionType,
|
|
||||||
};
|
|
||||||
use crate::Db;
|
|
||||||
|
|
||||||
/// A list of formal type variables for a generic function, class, or type alias.
|
|
||||||
///
|
|
||||||
/// TODO: Handle nested generic contexts better, with actual parent links to the lexically
|
|
||||||
/// containing context.
|
|
||||||
#[salsa::interned(debug)]
|
|
||||||
pub struct GenericContext<'db> {
|
|
||||||
#[return_ref]
|
|
||||||
pub(crate) variables: Box<[TypeVarInstance<'db>]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> GenericContext<'db> {
|
|
||||||
pub(crate) fn from_type_params(
|
|
||||||
db: &'db dyn Db,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
type_params_node: &ast::TypeParams,
|
|
||||||
) -> Self {
|
|
||||||
let variables: Box<[_]> = type_params_node
|
|
||||||
.iter()
|
|
||||||
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
|
|
||||||
.collect();
|
|
||||||
Self::new(db, variables)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variable_from_type_param(
|
|
||||||
db: &'db dyn Db,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
type_param_node: &ast::TypeParam,
|
|
||||||
) -> Option<TypeVarInstance<'db>> {
|
|
||||||
match type_param_node {
|
|
||||||
ast::TypeParam::TypeVar(node) => {
|
|
||||||
let definition = index.expect_single_definition(node);
|
|
||||||
let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) =
|
|
||||||
declaration_type(db, definition).inner_type()
|
|
||||||
else {
|
|
||||||
panic!("typevar should be inferred as a TypeVarInstance");
|
|
||||||
};
|
|
||||||
Some(typevar)
|
|
||||||
}
|
|
||||||
// TODO: Support these!
|
|
||||||
ast::TypeParam::ParamSpec(_) => None,
|
|
||||||
ast::TypeParam::TypeVarTuple(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> {
|
|
||||||
let parameters = Parameters::new(
|
|
||||||
self.variables(db)
|
|
||||||
.iter()
|
|
||||||
.map(|typevar| Self::parameter_from_typevar(db, *typevar)),
|
|
||||||
);
|
|
||||||
Signature::new(parameters, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parameter_from_typevar(db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Parameter<'db> {
|
|
||||||
let mut parameter = Parameter::positional_only(Some(typevar.name(db).clone()));
|
|
||||||
match typevar.bound_or_constraints(db) {
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
|
||||||
// TODO: This should be a type form.
|
|
||||||
parameter = parameter.with_annotated_type(bound);
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
// TODO: This should be a new type variant where only these exact types are
|
|
||||||
// assignable, and not subclasses of them, nor a union of them.
|
|
||||||
parameter = parameter
|
|
||||||
.with_annotated_type(UnionType::from_elements(db, constraints.iter(db)));
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
|
||||||
let types = self
|
|
||||||
.variables(db)
|
|
||||||
.iter()
|
|
||||||
.map(|typevar| typevar.default_ty(db).unwrap_or(Type::unknown()))
|
|
||||||
.collect();
|
|
||||||
self.specialize(db, types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
|
||||||
let types = self
|
|
||||||
.variables(db)
|
|
||||||
.iter()
|
|
||||||
.map(|typevar| Type::TypeVar(*typevar))
|
|
||||||
.collect();
|
|
||||||
self.specialize(db, types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
|
||||||
let types = vec![Type::unknown(); self.variables(db).len()];
|
|
||||||
self.specialize(db, types.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn specialize(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
types: Box<[Type<'db>]>,
|
|
||||||
) -> Specialization<'db> {
|
|
||||||
Specialization::new(db, self, types)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An assignment of a specific type to each type variable in a generic scope.
|
|
||||||
///
|
|
||||||
/// TODO: Handle nested specializations better, with actual parent links to the specialization of
|
|
||||||
/// the lexically containing context.
|
|
||||||
#[salsa::interned(debug)]
|
|
||||||
pub struct Specialization<'db> {
|
|
||||||
pub(crate) generic_context: GenericContext<'db>,
|
|
||||||
#[return_ref]
|
|
||||||
pub(crate) types: Box<[Type<'db>]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> Specialization<'db> {
|
|
||||||
/// Applies a specialization to this specialization. This is used, for instance, when a generic
|
|
||||||
/// class inherits from a generic alias:
|
|
||||||
///
|
|
||||||
/// ```py
|
|
||||||
/// class A[T]: ...
|
|
||||||
/// class B[U](A[U]): ...
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// `B` is a generic class, whose MRO includes the generic alias `A[U]`, which specializes `A`
|
|
||||||
/// with the specialization `{T: U}`. If `B` is specialized to `B[int]`, with specialization
|
|
||||||
/// `{U: int}`, we can apply the second specialization to the first, resulting in `T: int`.
|
|
||||||
/// That lets us produce the generic alias `A[int]`, which is the corresponding entry in the
|
|
||||||
/// MRO of `B[int]`.
|
|
||||||
pub(crate) fn apply_specialization(self, db: &'db dyn Db, other: Specialization<'db>) -> Self {
|
|
||||||
let types: Box<[_]> = self
|
|
||||||
.types(db)
|
|
||||||
.into_iter()
|
|
||||||
.map(|ty| ty.apply_specialization(db, other))
|
|
||||||
.collect();
|
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combines two specializations of the same generic context. If either specialization maps a
|
|
||||||
/// typevar to `Type::Unknown`, the other specialization's mapping is used. If both map the
|
|
||||||
/// typevar to a known type, those types are unioned together.
|
|
||||||
///
|
|
||||||
/// Panics if the two specializations are not for the same generic context.
|
|
||||||
pub(crate) fn combine(self, db: &'db dyn Db, other: Self) -> Self {
|
|
||||||
let generic_context = self.generic_context(db);
|
|
||||||
assert!(other.generic_context(db) == generic_context);
|
|
||||||
let types: Box<[_]> = self
|
|
||||||
.types(db)
|
|
||||||
.into_iter()
|
|
||||||
.zip(other.types(db))
|
|
||||||
.map(|(self_type, other_type)| match (self_type, other_type) {
|
|
||||||
(unknown, known) | (known, unknown) if unknown.is_unknown() => *known,
|
|
||||||
_ => UnionType::from_elements(db, [self_type, other_type]),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
|
||||||
let types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect();
|
|
||||||
Self::new(db, self.generic_context(db), types)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the type that a typevar is specialized to, or None if the typevar isn't part of
|
|
||||||
/// this specialization.
|
|
||||||
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
|
||||||
self.generic_context(db)
|
|
||||||
.variables(db)
|
|
||||||
.into_iter()
|
|
||||||
.zip(self.types(db))
|
|
||||||
.find(|(var, _)| **var == typevar)
|
|
||||||
.map(|(_, ty)| *ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
|
||||||
let generic_context = self.generic_context(db);
|
|
||||||
if generic_context != other.generic_context(db) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
|
||||||
.zip(self.types(db))
|
|
||||||
.zip(other.types(db))
|
|
||||||
{
|
|
||||||
if matches!(self_type, Type::Dynamic(_)) || matches!(other_type, Type::Dynamic(_)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We currently treat all typevars as invariant. Once we track the actual
|
|
||||||
// variance of each typevar, these checks should change:
|
|
||||||
// - covariant: verify that self_type <: other_type
|
|
||||||
// - contravariant: verify that other_type <: self_type
|
|
||||||
// - invariant: verify that self_type == other_type
|
|
||||||
// - bivariant: skip, can't make subtyping false
|
|
||||||
if !self_type.is_equivalent_to(db, *other_type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
|
||||||
let generic_context = self.generic_context(db);
|
|
||||||
if generic_context != other.generic_context(db) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
|
||||||
.zip(self.types(db))
|
|
||||||
.zip(other.types(db))
|
|
||||||
{
|
|
||||||
if matches!(self_type, Type::Dynamic(_)) || matches!(other_type, Type::Dynamic(_)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We currently treat all typevars as invariant. Once we track the actual
|
|
||||||
// variance of each typevar, these checks should change:
|
|
||||||
// - covariant: verify that self_type == other_type
|
|
||||||
// - contravariant: verify that other_type == self_type
|
|
||||||
// - invariant: verify that self_type == other_type
|
|
||||||
// - bivariant: skip, can't make equivalence false
|
|
||||||
if !self_type.is_equivalent_to(db, *other_type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
|
||||||
let generic_context = self.generic_context(db);
|
|
||||||
if generic_context != other.generic_context(db) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
|
||||||
.zip(self.types(db))
|
|
||||||
.zip(other.types(db))
|
|
||||||
{
|
|
||||||
if matches!(self_type, Type::Dynamic(_)) || matches!(other_type, Type::Dynamic(_)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We currently treat all typevars as invariant. Once we track the actual
|
|
||||||
// variance of each typevar, these checks should change:
|
|
||||||
// - covariant: verify that self_type <: other_type
|
|
||||||
// - contravariant: verify that other_type <: self_type
|
|
||||||
// - invariant: verify that self_type == other_type
|
|
||||||
// - bivariant: skip, can't make assignability false
|
|
||||||
if !self_type.is_gradual_equivalent_to(db, *other_type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_gradual_equivalent_to(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
other: Specialization<'db>,
|
|
||||||
) -> bool {
|
|
||||||
let generic_context = self.generic_context(db);
|
|
||||||
if generic_context != other.generic_context(db) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
|
||||||
.zip(self.types(db))
|
|
||||||
.zip(other.types(db))
|
|
||||||
{
|
|
||||||
// TODO: We currently treat all typevars as invariant. Once we track the actual
|
|
||||||
// variance of each typevar, these checks should change:
|
|
||||||
// - covariant: verify that self_type == other_type
|
|
||||||
// - contravariant: verify that other_type == self_type
|
|
||||||
// - invariant: verify that self_type == other_type
|
|
||||||
// - bivariant: skip, can't make equivalence false
|
|
||||||
if !self_type.is_gradual_equivalent_to(db, *other_type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs type inference between parameter annotations and argument types, producing a
|
|
||||||
/// specialization of a generic function.
|
|
||||||
pub(crate) struct SpecializationBuilder<'db> {
|
|
||||||
db: &'db dyn Db,
|
|
||||||
generic_context: GenericContext<'db>,
|
|
||||||
types: FxHashMap<TypeVarInstance<'db>, UnionBuilder<'db>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> SpecializationBuilder<'db> {
|
|
||||||
pub(crate) fn new(db: &'db dyn Db, generic_context: GenericContext<'db>) -> Self {
|
|
||||||
Self {
|
|
||||||
db,
|
|
||||||
generic_context,
|
|
||||||
types: FxHashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build(mut self) -> Specialization<'db> {
|
|
||||||
let types: Box<[_]> = self
|
|
||||||
.generic_context
|
|
||||||
.variables(self.db)
|
|
||||||
.iter()
|
|
||||||
.map(|variable| {
|
|
||||||
self.types
|
|
||||||
.remove(variable)
|
|
||||||
.map(UnionBuilder::build)
|
|
||||||
.unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown()))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Specialization::new(self.db, self.generic_context, types)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) {
|
|
||||||
let builder = self
|
|
||||||
.types
|
|
||||||
.entry(typevar)
|
|
||||||
.or_insert_with(|| UnionBuilder::new(self.db));
|
|
||||||
builder.add_in_place(ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn infer(&mut self, formal: Type<'db>, actual: Type<'db>) {
|
|
||||||
// If the actual type is already assignable to the formal type, then return without adding
|
|
||||||
// any new type mappings. (Note that if the formal type contains any typevars, this check
|
|
||||||
// will fail, since no non-typevar types are assignable to a typevar.)
|
|
||||||
//
|
|
||||||
// In particular, this handles a case like
|
|
||||||
//
|
|
||||||
// ```py
|
|
||||||
// def f[T](t: T | None): ...
|
|
||||||
//
|
|
||||||
// f(None)
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// without specializing `T` to `None`.
|
|
||||||
if actual.is_assignable_to(self.db, formal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match (formal, actual) {
|
|
||||||
(Type::TypeVar(typevar), _) => self.add_type_mapping(typevar, actual),
|
|
||||||
|
|
||||||
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
|
|
||||||
let formal_elements = formal_tuple.elements(self.db);
|
|
||||||
let actual_elements = actual_tuple.elements(self.db);
|
|
||||||
if formal_elements.len() == actual_elements.len() {
|
|
||||||
for (formal_element, actual_element) in
|
|
||||||
formal_elements.iter().zip(actual_elements)
|
|
||||||
{
|
|
||||||
self.infer(*formal_element, *actual_element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Type::Union(formal), _) => {
|
|
||||||
// TODO: We haven't implemented a full unification solver yet. If typevars appear
|
|
||||||
// in multiple union elements, we ideally want to express that _only one_ of them
|
|
||||||
// needs to match, and that we should infer the smallest type mapping that allows
|
|
||||||
// that.
|
|
||||||
//
|
|
||||||
// For now, we punt on handling multiple typevar elements. Instead, if _precisely
|
|
||||||
// one_ union element _is_ a typevar (not _contains_ a typevar), then we go ahead
|
|
||||||
// and add a mapping between that typevar and the actual type. (Note that we've
|
|
||||||
// already handled above the case where the actual is assignable to a _non-typevar_
|
|
||||||
// union element.)
|
|
||||||
let mut typevars = formal.iter(self.db).filter_map(|ty| match ty {
|
|
||||||
Type::TypeVar(typevar) => Some(*typevar),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
let typevar = typevars.next();
|
|
||||||
let additional_typevars = typevars.next();
|
|
||||||
if let (Some(typevar), None) = (typevar, additional_typevars) {
|
|
||||||
self.add_type_mapping(typevar, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Type::Intersection(formal), _) => {
|
|
||||||
// The actual type must be assignable to every (positive) element of the
|
|
||||||
// formal intersection, so we must infer type mappings for each of them. (The
|
|
||||||
// actual type must also be disjoint from every negative element of the
|
|
||||||
// intersection, but that doesn't help us infer any type mappings.)
|
|
||||||
for positive in formal.iter_positive(self.db) {
|
|
||||||
self.infer(positive, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more forms that we can structurally induct into: type[C], callables
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use lsp_types::request::DocumentDiagnosticRequest;
|
|
||||||
use lsp_types::{
|
|
||||||
Diagnostic, DiagnosticSeverity, DocumentDiagnosticParams, DocumentDiagnosticReport,
|
|
||||||
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, NumberOrString, Range,
|
|
||||||
RelatedFullDocumentDiagnosticReport, Url,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::document::ToRangeExt;
|
|
||||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
|
||||||
use crate::server::{client::Notifier, Result};
|
|
||||||
use crate::session::DocumentSnapshot;
|
|
||||||
use red_knot_project::{Db, ProjectDatabase};
|
|
||||||
use ruff_db::diagnostic::Severity;
|
|
||||||
use ruff_db::source::{line_index, source_text};
|
|
||||||
|
|
||||||
pub(crate) struct DocumentDiagnosticRequestHandler;
|
|
||||||
|
|
||||||
impl RequestHandler for DocumentDiagnosticRequestHandler {
|
|
||||||
type RequestType = DocumentDiagnosticRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
|
|
||||||
fn document_url(params: &DocumentDiagnosticParams) -> Cow<Url> {
|
|
||||||
Cow::Borrowed(¶ms.text_document.uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_with_snapshot(
|
|
||||||
snapshot: DocumentSnapshot,
|
|
||||||
db: ProjectDatabase,
|
|
||||||
_notifier: Notifier,
|
|
||||||
_params: DocumentDiagnosticParams,
|
|
||||||
) -> Result<DocumentDiagnosticReportResult> {
|
|
||||||
let diagnostics = compute_diagnostics(&snapshot, &db);
|
|
||||||
|
|
||||||
Ok(DocumentDiagnosticReportResult::Report(
|
|
||||||
DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport {
|
|
||||||
related_documents: None,
|
|
||||||
full_document_diagnostic_report: FullDocumentDiagnosticReport {
|
|
||||||
result_id: None,
|
|
||||||
items: diagnostics,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &ProjectDatabase) -> Vec<Diagnostic> {
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
|
||||||
tracing::info!(
|
|
||||||
"No file found for snapshot for `{}`",
|
|
||||||
snapshot.query().file_url()
|
|
||||||
);
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
|
|
||||||
let diagnostics = match db.check_file(file) {
|
|
||||||
Ok(diagnostics) => diagnostics,
|
|
||||||
Err(cancelled) => {
|
|
||||||
tracing::info!("Diagnostics computation {cancelled}");
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
diagnostics
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.map(|message| to_lsp_diagnostic(db, message, snapshot.encoding()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_lsp_diagnostic(
|
|
||||||
db: &dyn Db,
|
|
||||||
diagnostic: &ruff_db::diagnostic::Diagnostic,
|
|
||||||
encoding: crate::PositionEncoding,
|
|
||||||
) -> Diagnostic {
|
|
||||||
let range = if let Some(span) = diagnostic.primary_span() {
|
|
||||||
let index = line_index(db.upcast(), span.file());
|
|
||||||
let source = source_text(db.upcast(), span.file());
|
|
||||||
|
|
||||||
span.range()
|
|
||||||
.map(|range| range.to_lsp_range(&source, &index, encoding))
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
Range::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let severity = match diagnostic.severity() {
|
|
||||||
Severity::Info => DiagnosticSeverity::INFORMATION,
|
|
||||||
Severity::Warning => DiagnosticSeverity::WARNING,
|
|
||||||
Severity::Error | Severity::Fatal => DiagnosticSeverity::ERROR,
|
|
||||||
};
|
|
||||||
|
|
||||||
Diagnostic {
|
|
||||||
range,
|
|
||||||
severity: Some(severity),
|
|
||||||
tags: None,
|
|
||||||
code: Some(NumberOrString::String(diagnostic.id().to_string())),
|
|
||||||
code_description: None,
|
|
||||||
source: Some("red-knot".into()),
|
|
||||||
message: diagnostic.concise_message().to_string(),
|
|
||||||
related_information: None,
|
|
||||||
data: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
The `knot_extensions.pyi` file in this directory will be symlinked into
|
|
||||||
the `vendor/typeshed/stdlib` directory every time we sync our `typeshed`
|
|
||||||
stubs (see `.github/workflows/sync_typeshed.yaml`).
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
f65bdc1acde54fda93c802459280da74518d2eef
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
def __getattr__(name: str) -> Any: ...
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import queue
|
|
||||||
from collections.abc import Callable, Iterable, Mapping, Set as AbstractSet
|
|
||||||
from threading import Lock, Semaphore, Thread
|
|
||||||
from types import GenericAlias
|
|
||||||
from typing import Any, Generic, TypeVar, overload
|
|
||||||
from typing_extensions import TypeVarTuple, Unpack
|
|
||||||
from weakref import ref
|
|
||||||
|
|
||||||
from ._base import BrokenExecutor, Executor, Future
|
|
||||||
|
|
||||||
_Ts = TypeVarTuple("_Ts")
|
|
||||||
|
|
||||||
_threads_queues: Mapping[Any, Any]
|
|
||||||
_shutdown: bool
|
|
||||||
_global_shutdown_lock: Lock
|
|
||||||
|
|
||||||
def _python_exit() -> None: ...
|
|
||||||
|
|
||||||
_S = TypeVar("_S")
|
|
||||||
|
|
||||||
class _WorkItem(Generic[_S]):
|
|
||||||
future: Future[_S]
|
|
||||||
fn: Callable[..., _S]
|
|
||||||
args: Iterable[Any]
|
|
||||||
kwargs: Mapping[str, Any]
|
|
||||||
def __init__(self, future: Future[_S], fn: Callable[..., _S], args: Iterable[Any], kwargs: Mapping[str, Any]) -> None: ...
|
|
||||||
def run(self) -> None: ...
|
|
||||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
|
||||||
|
|
||||||
def _worker(
|
|
||||||
executor_reference: ref[Any],
|
|
||||||
work_queue: queue.SimpleQueue[Any],
|
|
||||||
initializer: Callable[[Unpack[_Ts]], object],
|
|
||||||
initargs: tuple[Unpack[_Ts]],
|
|
||||||
) -> None: ...
|
|
||||||
|
|
||||||
class BrokenThreadPool(BrokenExecutor): ...
|
|
||||||
|
|
||||||
class ThreadPoolExecutor(Executor):
|
|
||||||
_max_workers: int
|
|
||||||
_idle_semaphore: Semaphore
|
|
||||||
_threads: AbstractSet[Thread]
|
|
||||||
_broken: bool
|
|
||||||
_shutdown: bool
|
|
||||||
_shutdown_lock: Lock
|
|
||||||
_thread_name_prefix: str | None
|
|
||||||
_initializer: Callable[..., None] | None
|
|
||||||
_initargs: tuple[Any, ...]
|
|
||||||
_work_queue: queue.SimpleQueue[_WorkItem[Any]]
|
|
||||||
@overload
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
max_workers: int | None = None,
|
|
||||||
thread_name_prefix: str = "",
|
|
||||||
initializer: Callable[[], object] | None = None,
|
|
||||||
initargs: tuple[()] = (),
|
|
||||||
) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
max_workers: int | None = None,
|
|
||||||
thread_name_prefix: str = "",
|
|
||||||
*,
|
|
||||||
initializer: Callable[[Unpack[_Ts]], object],
|
|
||||||
initargs: tuple[Unpack[_Ts]],
|
|
||||||
) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
max_workers: int | None,
|
|
||||||
thread_name_prefix: str,
|
|
||||||
initializer: Callable[[Unpack[_Ts]], object],
|
|
||||||
initargs: tuple[Unpack[_Ts]],
|
|
||||||
) -> None: ...
|
|
||||||
def _adjust_thread_count(self) -> None: ...
|
|
||||||
def _initializer_failed(self) -> None: ...
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from typing import TextIO
|
|
||||||
|
|
||||||
__all__ = ["getpass", "getuser", "GetPassWarning"]
|
|
||||||
|
|
||||||
def getpass(prompt: str = "Password: ", stream: TextIO | None = None) -> str: ...
|
|
||||||
def getuser() -> str: ...
|
|
||||||
|
|
||||||
class GetPassWarning(UserWarning): ...
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
def url2pathname(url: str) -> str: ...
|
|
||||||
def pathname2url(p: str) -> str: ...
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
from _typeshed import SupportsRead
|
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
__all__ = ("loads", "load", "TOMLDecodeError")
|
|
||||||
|
|
||||||
class TOMLDecodeError(ValueError): ...
|
|
||||||
|
|
||||||
def load(fp: SupportsRead[bytes], /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ...
|
|
||||||
def loads(s: str, /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ...
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.11.6"
|
version = "0.11.11"
|
||||||
publish = true
|
publish = true
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -20,6 +20,7 @@ ruff_graph = { workspace = true, features = ["serde", "clap"] }
|
|||||||
ruff_linter = { workspace = true, features = ["clap"] }
|
ruff_linter = { workspace = true, features = ["clap"] }
|
||||||
ruff_macros = { workspace = true }
|
ruff_macros = { workspace = true }
|
||||||
ruff_notebook = { workspace = true }
|
ruff_notebook = { workspace = true }
|
||||||
|
ruff_options_metadata = { workspace = true, features = ["serde"] }
|
||||||
ruff_python_ast = { workspace = true }
|
ruff_python_ast = { workspace = true }
|
||||||
ruff_python_formatter = { workspace = true }
|
ruff_python_formatter = { workspace = true }
|
||||||
ruff_python_parser = { workspace = true }
|
ruff_python_parser = { workspace = true }
|
||||||
@@ -30,7 +31,7 @@ ruff_workspace = { workspace = true }
|
|||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
argfile = { workspace = true }
|
argfile = { workspace = true }
|
||||||
bincode = { workspace = true }
|
bincode = { workspace = true, features = ["serde"] }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
cachedir = { workspace = true }
|
cachedir = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||||
@@ -77,10 +78,13 @@ test-case = { workspace = true }
|
|||||||
# Used via macro expansion.
|
# Used via macro expansion.
|
||||||
ignored = ["jiff"]
|
ignored = ["jiff"]
|
||||||
|
|
||||||
|
[package.metadata.dist]
|
||||||
|
dist = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
mimalloc = { workspace = true }
|
mimalloc = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||||
tikv-jemallocator = { workspace = true }
|
tikv-jemallocator = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ fn main() {
|
|||||||
|
|
||||||
commit_info(&workspace_root);
|
commit_info(&workspace_root);
|
||||||
|
|
||||||
#[allow(clippy::disallowed_methods)]
|
|
||||||
let target = std::env::var("TARGET").unwrap();
|
let target = std::env::var("TARGET").unwrap();
|
||||||
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
|
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::commands::completions::config::{OptionString, OptionStringParser};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||||
use clap::{command, Parser, Subcommand};
|
use clap::{Parser, Subcommand, command};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
@@ -21,18 +22,16 @@ use ruff_linter::settings::types::{
|
|||||||
PythonVersion, UnsafeFixes,
|
PythonVersion, UnsafeFixes,
|
||||||
};
|
};
|
||||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||||
|
use ruff_options_metadata::{OptionEntry, OptionsMetadata};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_source_file::{LineIndex, OneIndexed};
|
use ruff_source_file::{LineIndex, OneIndexed, PositionEncoding};
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
use ruff_workspace::options::{Options, PycodestyleOptions};
|
||||||
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
|
|
||||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use crate::commands::completions::config::{OptionString, OptionStringParser};
|
|
||||||
|
|
||||||
/// All configuration options that can be passed "globally",
|
/// All configuration options that can be passed "globally",
|
||||||
/// i.e., can be passed to all subcommands
|
/// i.e., can be passed to all subcommands
|
||||||
#[derive(Debug, Default, Clone, clap::Args)]
|
#[derive(Debug, Default, Clone, clap::Args)]
|
||||||
@@ -94,7 +93,7 @@ pub struct Args {
|
|||||||
pub(crate) global_options: GlobalConfigArgs,
|
pub(crate) global_options: GlobalConfigArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[expect(clippy::large_enum_variant)]
|
||||||
#[derive(Debug, clap::Subcommand)]
|
#[derive(Debug, clap::Subcommand)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Run Ruff on the given files or directories.
|
/// Run Ruff on the given files or directories.
|
||||||
@@ -178,11 +177,14 @@ pub struct AnalyzeGraphCommand {
|
|||||||
/// The minimum Python version that should be supported.
|
/// The minimum Python version that should be supported.
|
||||||
#[arg(long, value_enum)]
|
#[arg(long, value_enum)]
|
||||||
target_version: Option<PythonVersion>,
|
target_version: Option<PythonVersion>,
|
||||||
|
/// Path to a virtual environment to use for resolving additional dependencies
|
||||||
|
#[arg(long)]
|
||||||
|
python: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
||||||
#[derive(Clone, Debug, clap::Parser)]
|
#[derive(Clone, Debug, clap::Parser)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[expect(clippy::struct_excessive_bools)]
|
||||||
pub struct CheckCommand {
|
pub struct CheckCommand {
|
||||||
/// List of files or directories to check.
|
/// List of files or directories to check.
|
||||||
#[clap(help = "List of files or directories to check [default: .]")]
|
#[clap(help = "List of files or directories to check [default: .]")]
|
||||||
@@ -444,7 +446,7 @@ pub struct CheckCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, clap::Parser)]
|
#[derive(Clone, Debug, clap::Parser)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[expect(clippy::struct_excessive_bools)]
|
||||||
pub struct FormatCommand {
|
pub struct FormatCommand {
|
||||||
/// List of files or directories to format.
|
/// List of files or directories to format.
|
||||||
#[clap(help = "List of files or directories to format [default: .]")]
|
#[clap(help = "List of files or directories to format [default: .]")]
|
||||||
@@ -558,7 +560,7 @@ pub enum HelpFormat {
|
|||||||
Json,
|
Json,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[expect(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, Default, Clone, clap::Args)]
|
#[derive(Debug, Default, Clone, clap::Args)]
|
||||||
pub struct LogLevelArgs {
|
pub struct LogLevelArgs {
|
||||||
/// Enable verbose logging.
|
/// Enable verbose logging.
|
||||||
@@ -797,6 +799,7 @@ impl AnalyzeGraphCommand {
|
|||||||
let format_arguments = AnalyzeGraphArgs {
|
let format_arguments = AnalyzeGraphArgs {
|
||||||
files: self.files,
|
files: self.files,
|
||||||
direction: self.direction,
|
direction: self.direction,
|
||||||
|
python: self.python,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cli_overrides = ExplicitConfigOverrides {
|
let cli_overrides = ExplicitConfigOverrides {
|
||||||
@@ -1028,7 +1031,7 @@ Possible choices:
|
|||||||
|
|
||||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||||
/// etc.).
|
/// etc.).
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[expect(clippy::struct_excessive_bools)]
|
||||||
pub struct CheckArguments {
|
pub struct CheckArguments {
|
||||||
pub add_noqa: bool,
|
pub add_noqa: bool,
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
@@ -1047,7 +1050,7 @@ pub struct CheckArguments {
|
|||||||
|
|
||||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||||
/// etc.).
|
/// etc.).
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[expect(clippy::struct_excessive_bools)]
|
||||||
pub struct FormatArguments {
|
pub struct FormatArguments {
|
||||||
pub check: bool,
|
pub check: bool,
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
@@ -1070,8 +1073,9 @@ impl FormatRange {
|
|||||||
///
|
///
|
||||||
/// Returns an empty range if the start range is past the end of `source`.
|
/// Returns an empty range if the start range is past the end of `source`.
|
||||||
pub(super) fn to_text_range(self, source: &str, line_index: &LineIndex) -> TextRange {
|
pub(super) fn to_text_range(self, source: &str, line_index: &LineIndex) -> TextRange {
|
||||||
let start_byte_offset = line_index.offset(self.start.line, self.start.column, source);
|
let start_byte_offset =
|
||||||
let end_byte_offset = line_index.offset(self.end.line, self.end.column, source);
|
line_index.offset(self.start.into(), source, PositionEncoding::Utf32);
|
||||||
|
let end_byte_offset = line_index.offset(self.end.into(), source, PositionEncoding::Utf32);
|
||||||
|
|
||||||
TextRange::new(start_byte_offset, end_byte_offset)
|
TextRange::new(start_byte_offset, end_byte_offset)
|
||||||
}
|
}
|
||||||
@@ -1122,10 +1126,10 @@ impl std::fmt::Display for FormatRangeParseError {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"the start position '{start_invalid}' is greater than the end position '{end_invalid}'.\n {tip} Try switching start and end: '{end}-{start}'",
|
"the start position '{start_invalid}' is greater than the end position '{end_invalid}'.\n {tip} Try switching start and end: '{end}-{start}'",
|
||||||
start_invalid=start.to_string().bold().yellow(),
|
start_invalid = start.to_string().bold().yellow(),
|
||||||
end_invalid=end.to_string().bold().yellow(),
|
end_invalid = end.to_string().bold().yellow(),
|
||||||
start=start.to_string().green().bold(),
|
start = start.to_string().green().bold(),
|
||||||
end=end.to_string().green().bold()
|
end = end.to_string().green().bold()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
|
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
|
||||||
@@ -1142,6 +1146,15 @@ pub struct LineColumn {
|
|||||||
pub column: OneIndexed,
|
pub column: OneIndexed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LineColumn> for ruff_source_file::SourceLocation {
|
||||||
|
fn from(value: LineColumn) -> Self {
|
||||||
|
Self {
|
||||||
|
line: value.line,
|
||||||
|
character_offset: value.column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for LineColumn {
|
impl std::fmt::Display for LineColumn {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{line}:{column}", line = self.line, column = self.column)
|
write!(f, "{line}:{column}", line = self.line, column = self.column)
|
||||||
@@ -1217,30 +1230,36 @@ impl LineColumnParseError {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
LineColumnParseError::ColumnParseError(inner) => {
|
LineColumnParseError::ColumnParseError(inner) => {
|
||||||
write!(f, "the {range}s column is not a valid number ({inner})'\n {tip} The format is 'line:column'.")
|
write!(
|
||||||
|
f,
|
||||||
|
"the {range}s column is not a valid number ({inner})'\n {tip} The format is 'line:column'."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LineColumnParseError::LineParseError(inner) => {
|
LineColumnParseError::LineParseError(inner) => {
|
||||||
write!(f, "the {range} line is not a valid number ({inner})\n {tip} The format is 'line:column'.")
|
write!(
|
||||||
|
f,
|
||||||
|
"the {range} line is not a valid number ({inner})\n {tip} The format is 'line:column'."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LineColumnParseError::ZeroColumnIndex { line } => {
|
LineColumnParseError::ZeroColumnIndex { line } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"the {range} column is 0, but it should be 1 or greater.\n {tip} The column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
"the {range} column is 0, but it should be 1 or greater.\n {tip} The column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
||||||
suggestion=format!("{line}:1").green().bold()
|
suggestion = format!("{line}:1").green().bold()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LineColumnParseError::ZeroLineIndex { column } => {
|
LineColumnParseError::ZeroLineIndex { column } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"the {range} line is 0, but it should be 1 or greater.\n {tip} The line numbers start at 1.\n {tip} Try {suggestion} instead.",
|
"the {range} line is 0, but it should be 1 or greater.\n {tip} The line numbers start at 1.\n {tip} Try {suggestion} instead.",
|
||||||
suggestion=format!("1:{column}").green().bold()
|
suggestion = format!("1:{column}").green().bold()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LineColumnParseError::ZeroLineAndColumnIndex => {
|
LineColumnParseError::ZeroLineAndColumnIndex => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"the {range} line and column are both 0, but they should be 1 or greater.\n {tip} The line and column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
"the {range} line and column are both 0, but they should be 1 or greater.\n {tip} The line and column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
||||||
suggestion="1:1".to_string().green().bold()
|
suggestion = "1:1".to_string().green().bold()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1252,12 +1271,12 @@ impl LineColumnParseError {
|
|||||||
pub struct AnalyzeGraphArgs {
|
pub struct AnalyzeGraphArgs {
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
|
pub python: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration overrides provided via dedicated CLI flags:
|
/// Configuration overrides provided via dedicated CLI flags:
|
||||||
/// `--line-length`, `--respect-gitignore`, etc.
|
/// `--line-length`, `--respect-gitignore`, etc.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
|
||||||
struct ExplicitConfigOverrides {
|
struct ExplicitConfigOverrides {
|
||||||
dummy_variable_rgx: Option<Regex>,
|
dummy_variable_rgx: Option<Regex>,
|
||||||
exclude: Option<Vec<FilePattern>>,
|
exclude: Option<Vec<FilePattern>>,
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use std::fs::{self, File};
|
|||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::io::{self, BufReader, Write};
|
use std::io::{self, BufReader, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
@@ -13,21 +13,21 @@ use itertools::Itertools;
|
|||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||||
|
use ruff_linter::codes::Rule;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
use ruff_diagnostics::Fix;
|
||||||
use ruff_linter::message::{DiagnosticMessage, Message};
|
use ruff_linter::message::Message;
|
||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::{warn_user, VERSION};
|
use ruff_linter::{VERSION, warn_user};
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
use ruff_notebook::NotebookIndex;
|
use ruff_notebook::NotebookIndex;
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::Resolver;
|
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
use ruff_workspace::resolver::Resolver;
|
||||||
|
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ pub(crate) struct Cache {
|
|||||||
changes: Mutex<Vec<Change>>,
|
changes: Mutex<Vec<Change>>,
|
||||||
/// The "current" timestamp used as cache for the updates of
|
/// The "current" timestamp used as cache for the updates of
|
||||||
/// [`FileCache::last_seen`]
|
/// [`FileCache::last_seen`]
|
||||||
#[allow(clippy::struct_field_names)]
|
#[expect(clippy::struct_field_names)]
|
||||||
last_seen_cache: u64,
|
last_seen_cache: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,13 +117,14 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut package: PackageCache = match bincode::deserialize_from(BufReader::new(file)) {
|
let mut package: PackageCache =
|
||||||
Ok(package) => package,
|
match bincode::decode_from_reader(BufReader::new(file), bincode::config::standard()) {
|
||||||
Err(err) => {
|
Ok(package) => package,
|
||||||
warn_user!("Failed parse cache file `{}`: {err}", path.display());
|
Err(err) => {
|
||||||
return Cache::empty(path, package_root);
|
warn_user!("Failed parse cache file `{}`: {err}", path.display());
|
||||||
}
|
return Cache::empty(path, package_root);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Sanity check.
|
// Sanity check.
|
||||||
if package.package_root != package_root {
|
if package.package_root != package_root {
|
||||||
@@ -146,7 +147,7 @@ impl Cache {
|
|||||||
Cache::new(path, package)
|
Cache::new(path, package)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[expect(clippy::cast_possible_truncation)]
|
||||||
fn new(path: PathBuf, package: PackageCache) -> Self {
|
fn new(path: PathBuf, package: PackageCache) -> Self {
|
||||||
Cache {
|
Cache {
|
||||||
path,
|
path,
|
||||||
@@ -175,8 +176,8 @@ impl Cache {
|
|||||||
|
|
||||||
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
|
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
|
||||||
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
|
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
|
||||||
let serialized =
|
let serialized = bincode::encode_to_vec(&self.package, bincode::config::standard())
|
||||||
bincode::serialize(&self.package).context("Failed to serialize cache data")?;
|
.context("Failed to serialize cache data")?;
|
||||||
temp_file
|
temp_file
|
||||||
.write_all(&serialized)
|
.write_all(&serialized)
|
||||||
.context("Failed to write serialized cache to temporary file.")?;
|
.context("Failed to write serialized cache to temporary file.")?;
|
||||||
@@ -204,7 +205,7 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the pending changes without storing the cache to disk.
|
/// Applies the pending changes without storing the cache to disk.
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[expect(clippy::cast_possible_truncation)]
|
||||||
pub(crate) fn save(&mut self) -> bool {
|
pub(crate) fn save(&mut self) -> bool {
|
||||||
/// Maximum duration for which we keep a file in cache that hasn't been seen.
|
/// Maximum duration for which we keep a file in cache that hasn't been seen.
|
||||||
const MAX_LAST_SEEN: Duration = Duration::from_secs(30 * 24 * 60 * 60); // 30 days.
|
const MAX_LAST_SEEN: Duration = Duration::from_secs(30 * 24 * 60 * 60); // 30 days.
|
||||||
@@ -311,7 +312,7 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// On disk representation of a cache of a package.
|
/// On disk representation of a cache of a package.
|
||||||
#[derive(Deserialize, Debug, Serialize)]
|
#[derive(bincode::Encode, Debug, bincode::Decode)]
|
||||||
struct PackageCache {
|
struct PackageCache {
|
||||||
/// Path to the root of the package.
|
/// Path to the root of the package.
|
||||||
///
|
///
|
||||||
@@ -323,7 +324,7 @@ struct PackageCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// On disk representation of the cache per source file.
|
/// On disk representation of the cache per source file.
|
||||||
#[derive(Deserialize, Debug, Serialize)]
|
#[derive(bincode::Decode, Debug, bincode::Encode)]
|
||||||
pub(crate) struct FileCache {
|
pub(crate) struct FileCache {
|
||||||
/// Key that determines if the cached item is still valid.
|
/// Key that determines if the cached item is still valid.
|
||||||
key: u64,
|
key: u64,
|
||||||
@@ -347,14 +348,16 @@ impl FileCache {
|
|||||||
lint.messages
|
lint.messages
|
||||||
.iter()
|
.iter()
|
||||||
.map(|msg| {
|
.map(|msg| {
|
||||||
Message::Diagnostic(DiagnosticMessage {
|
Message::diagnostic(
|
||||||
kind: msg.kind.clone(),
|
msg.rule.into(),
|
||||||
range: msg.range,
|
msg.body.clone(),
|
||||||
fix: msg.fix.clone(),
|
msg.suggestion.clone(),
|
||||||
file: file.clone(),
|
msg.range,
|
||||||
noqa_offset: msg.noqa_offset,
|
msg.fix.clone(),
|
||||||
parent: msg.parent,
|
msg.parent,
|
||||||
})
|
file.clone(),
|
||||||
|
msg.noqa_offset,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
@@ -368,7 +371,7 @@ impl FileCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, bincode::Decode, bincode::Encode)]
|
||||||
struct FileCacheData {
|
struct FileCacheData {
|
||||||
lint: Option<LintCacheData>,
|
lint: Option<LintCacheData>,
|
||||||
formatted: bool,
|
formatted: bool,
|
||||||
@@ -406,7 +409,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Serialize, PartialEq)]
|
#[derive(bincode::Decode, Debug, bincode::Encode, PartialEq)]
|
||||||
pub(crate) struct LintCacheData {
|
pub(crate) struct LintCacheData {
|
||||||
/// Imports made.
|
/// Imports made.
|
||||||
// pub(super) imports: ImportMap,
|
// pub(super) imports: ImportMap,
|
||||||
@@ -419,6 +422,7 @@ pub(crate) struct LintCacheData {
|
|||||||
/// This will be empty if `messages` is empty.
|
/// This will be empty if `messages` is empty.
|
||||||
pub(super) source: String,
|
pub(super) source: String,
|
||||||
/// Notebook index if this file is a Jupyter Notebook.
|
/// Notebook index if this file is a Jupyter Notebook.
|
||||||
|
#[bincode(with_serde)]
|
||||||
pub(super) notebook_index: Option<NotebookIndex>,
|
pub(super) notebook_index: Option<NotebookIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,20 +439,22 @@ impl LintCacheData {
|
|||||||
|
|
||||||
let messages = messages
|
let messages = messages
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|message| message.as_diagnostic_message())
|
.filter_map(|msg| msg.to_rule().map(|rule| (rule, msg)))
|
||||||
.map(|msg| {
|
.map(|(rule, msg)| {
|
||||||
// Make sure that all message use the same source file.
|
// Make sure that all message use the same source file.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&msg.file,
|
msg.source_file(),
|
||||||
messages.first().unwrap().source_file(),
|
messages.first().unwrap().source_file(),
|
||||||
"message uses a different source file"
|
"message uses a different source file"
|
||||||
);
|
);
|
||||||
CacheMessage {
|
CacheMessage {
|
||||||
kind: msg.kind.clone(),
|
rule,
|
||||||
range: msg.range,
|
body: msg.body().to_string(),
|
||||||
|
suggestion: msg.suggestion().map(ToString::to_string),
|
||||||
|
range: msg.range(),
|
||||||
parent: msg.parent,
|
parent: msg.parent,
|
||||||
fix: msg.fix.clone(),
|
fix: msg.fix().cloned(),
|
||||||
noqa_offset: msg.noqa_offset,
|
noqa_offset: msg.noqa_offset(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -462,14 +468,24 @@ impl LintCacheData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// On disk representation of a diagnostic message.
|
/// On disk representation of a diagnostic message.
|
||||||
#[derive(Deserialize, Debug, Serialize, PartialEq)]
|
#[derive(bincode::Decode, Debug, bincode::Encode, PartialEq)]
|
||||||
pub(super) struct CacheMessage {
|
pub(super) struct CacheMessage {
|
||||||
kind: DiagnosticKind,
|
/// The rule for the cached diagnostic.
|
||||||
|
#[bincode(with_serde)]
|
||||||
|
rule: Rule,
|
||||||
|
/// The message body to display to the user, to explain the diagnostic.
|
||||||
|
body: String,
|
||||||
|
/// The message to display to the user, to explain the suggested fix.
|
||||||
|
suggestion: Option<String>,
|
||||||
/// Range into the message's [`FileCache::source`].
|
/// Range into the message's [`FileCache::source`].
|
||||||
|
#[bincode(with_serde)]
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
|
#[bincode(with_serde)]
|
||||||
parent: Option<TextSize>,
|
parent: Option<TextSize>,
|
||||||
|
#[bincode(with_serde)]
|
||||||
fix: Option<Fix>,
|
fix: Option<Fix>,
|
||||||
noqa_offset: TextSize,
|
#[bincode(with_serde)]
|
||||||
|
noqa_offset: Option<TextSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait PackageCaches {
|
pub(crate) trait PackageCaches {
|
||||||
@@ -587,7 +603,7 @@ mod tests {
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use filetime::{set_file_mtime, FileTime};
|
use filetime::{FileTime, set_file_mtime};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_linter::settings::LinterSettings;
|
use ruff_linter::settings::LinterSettings;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
@@ -602,8 +618,8 @@ mod tests {
|
|||||||
|
|
||||||
use crate::cache::{self, FileCache, FileCacheData, FileCacheKey};
|
use crate::cache::{self, FileCache, FileCacheData, FileCacheKey};
|
||||||
use crate::cache::{Cache, RelativePathBuf};
|
use crate::cache::{Cache, RelativePathBuf};
|
||||||
use crate::commands::format::{format_path, FormatCommandError, FormatMode, FormatResult};
|
use crate::commands::format::{FormatCommandError, FormatMode, FormatResult, format_path};
|
||||||
use crate::diagnostics::{lint_path, Diagnostics};
|
use crate::diagnostics::{Diagnostics, lint_path};
|
||||||
|
|
||||||
#[test_case("../ruff_linter/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_linter"; "ruff_linter_fixtures")]
|
#[test_case("../ruff_linter/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_linter"; "ruff_linter_fixtures")]
|
||||||
#[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")]
|
#[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")]
|
||||||
@@ -616,7 +632,7 @@ mod tests {
|
|||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
cache_dir,
|
cache_dir,
|
||||||
linter: LinterSettings {
|
linter: LinterSettings {
|
||||||
unresolved_target_version: PythonVersion::latest(),
|
unresolved_target_version: PythonVersion::latest().into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
@@ -834,7 +850,6 @@ mod tests {
|
|||||||
// Regression test for issue #3086.
|
// Regression test for issue #3086.
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[allow(clippy::items_after_statements)]
|
|
||||||
fn flip_execute_permission_bit(path: &Path) -> io::Result<()> {
|
fn flip_execute_permission_bit(path: &Path) -> io::Result<()> {
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
let file = fs::OpenOptions::new().write(true).open(path)?;
|
let file = fs::OpenOptions::new().write(true).open(path)?;
|
||||||
@@ -843,7 +858,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[allow(clippy::items_after_statements)]
|
|
||||||
fn flip_read_only_permission(path: &Path) -> io::Result<()> {
|
fn flip_read_only_permission(path: &Path) -> io::Result<()> {
|
||||||
let file = fs::OpenOptions::new().write(true).open(path)?;
|
let file = fs::OpenOptions::new().write(true).open(path)?;
|
||||||
let mut perms = file.metadata()?.permissions();
|
let mut perms = file.metadata()?.permissions();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use ruff_linter::source_kind::SourceKind;
|
|||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{
|
use ruff_workspace::resolver::{
|
||||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::args::{AnalyzeGraphArgs, ConfigArguments};
|
use crate::args::{AnalyzeGraphArgs, ConfigArguments};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::{resolve_default_files, ExitStatus};
|
use crate::{ExitStatus, resolve_default_files};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use path_absolutize::CWD;
|
use path_absolutize::CWD;
|
||||||
@@ -9,7 +9,7 @@ use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
|||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::{warn_user, warn_user_once};
|
use ruff_linter::{warn_user, warn_user_once};
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile};
|
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -75,6 +75,8 @@ pub(crate) fn analyze_graph(
|
|||||||
.target_version
|
.target_version
|
||||||
.as_tuple()
|
.as_tuple()
|
||||||
.into(),
|
.into(),
|
||||||
|
args.python
|
||||||
|
.and_then(|python| SystemPathBuf::from_path_buf(python).ok()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let imports = {
|
let imports = {
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ use ruff_linter::message::Message;
|
|||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::registry::Rule;
|
use ruff_linter::registry::Rule;
|
||||||
use ruff_linter::settings::types::UnsafeFixes;
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{LinterSettings, flags};
|
||||||
use ruff_linter::{fs, warn_user_once, IOError};
|
use ruff_linter::{IOError, fs, warn_user_once};
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::TextRange;
|
||||||
use ruff_workspace::resolver::{
|
use ruff_workspace::resolver::{
|
||||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
@@ -30,7 +30,6 @@ use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
|||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
|
|
||||||
/// Run the linter over a collection of files.
|
/// Run the linter over a collection of files.
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub(crate) fn check(
|
pub(crate) fn check(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
@@ -134,7 +133,7 @@ pub(crate) fn check(
|
|||||||
vec![Message::from_diagnostic(
|
vec![Message::from_diagnostic(
|
||||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||||
dummy,
|
dummy,
|
||||||
TextSize::default(),
|
None,
|
||||||
)],
|
)],
|
||||||
FxHashMap::default(),
|
FxHashMap::default(),
|
||||||
)
|
)
|
||||||
@@ -181,7 +180,6 @@ pub(crate) fn check(
|
|||||||
|
|
||||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||||
/// a diagnostic if the linting the file panics.
|
/// a diagnostic if the linting the file panics.
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn lint_path(
|
fn lint_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
package: Option<PackageRoot<'_>>,
|
package: Option<PackageRoot<'_>>,
|
||||||
@@ -230,9 +228,9 @@ mod test {
|
|||||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||||
use ruff_linter::registry::Rule;
|
use ruff_linter::registry::Rule;
|
||||||
use ruff_linter::settings::types::UnsafeFixes;
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{LinterSettings, flags};
|
||||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use anyhow::Result;
|
|||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::packaging;
|
use ruff_linter::packaging;
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags;
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, python_file_at_path};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
use crate::diagnostics::{Diagnostics, lint_stdin};
|
||||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||||
|
|
||||||
/// Run the linter over a single file, read from `stdin`.
|
/// Run the linter over a single file, read from `stdin`.
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ruff_workspace::{
|
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||||
options::Options,
|
use ruff_workspace::options::Options;
|
||||||
options_base::{OptionField, OptionSet, OptionsMetadata, Visit},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CollectOptionsVisitor {
|
struct CollectOptionsVisitor {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
|
|
||||||
use crate::args::HelpFormat;
|
use crate::args::HelpFormat;
|
||||||
|
|
||||||
|
use ruff_options_metadata::OptionsMetadata;
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
use ruff_workspace::options_base::OptionsMetadata;
|
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
#[expect(clippy::print_stdout)]
|
||||||
pub(crate) fn config(key: Option<&str>, format: HelpFormat) -> Result<()> {
|
pub(crate) fn config(key: Option<&str>, format: HelpFormat) -> Result<()> {
|
||||||
match key {
|
match key {
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{stderr, stdout, Write};
|
use std::io::{Write, stderr, stdout};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ use rustc_hash::FxHashSet;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use ruff_db::panic::{catch_unwind, PanicError};
|
use ruff_db::panic::{PanicError, catch_unwind};
|
||||||
use ruff_diagnostics::SourceMap;
|
use ruff_diagnostics::SourceMap;
|
||||||
use ruff_linter::fs;
|
use ruff_linter::fs;
|
||||||
use ruff_linter::logging::{DisplayParseError, LogLevel};
|
use ruff_linter::logging::{DisplayParseError, LogLevel};
|
||||||
@@ -26,16 +26,16 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
|
|||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_python_formatter::{format_module_source, format_range, FormatModuleError, QuoteStyle};
|
use ruff_python_formatter::{FormatModuleError, QuoteStyle, format_module_source, format_range};
|
||||||
use ruff_source_file::LineIndex;
|
use ruff_source_file::LineIndex;
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
use ruff_workspace::resolver::{ResolvedFile, Resolver, match_exclusion, python_files_in_path};
|
||||||
|
|
||||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||||
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::{resolve_default_files, ExitStatus};
|
use crate::{ExitStatus, resolve_default_files};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, is_macro::Is)]
|
#[derive(Debug, Copy, Clone, is_macro::Is)]
|
||||||
pub(crate) enum FormatMode {
|
pub(crate) enum FormatMode {
|
||||||
@@ -160,7 +160,7 @@ pub(crate) fn format(
|
|||||||
}),
|
}),
|
||||||
Err(error) => Err(FormatCommandError::Panic(
|
Err(error) => Err(FormatCommandError::Panic(
|
||||||
Some(resolved_file.path().to_path_buf()),
|
Some(resolved_file.path().to_path_buf()),
|
||||||
error,
|
Box::new(error),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -362,7 +362,7 @@ pub(crate) fn format_source(
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
|
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
|
||||||
#[allow(clippy::redundant_closure_for_method_calls)]
|
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||||
format_module_source(unformatted, options).map(|formatted| formatted.into_code())
|
format_module_source(unformatted, options).map(|formatted| formatted.into_code())
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -635,7 +635,7 @@ impl<'a> FormatResults<'a> {
|
|||||||
pub(crate) enum FormatCommandError {
|
pub(crate) enum FormatCommandError {
|
||||||
Ignore(#[from] ignore::Error),
|
Ignore(#[from] ignore::Error),
|
||||||
Parse(#[from] DisplayParseError),
|
Parse(#[from] DisplayParseError),
|
||||||
Panic(Option<PathBuf>, PanicError),
|
Panic(Option<PathBuf>, Box<PanicError>),
|
||||||
Read(Option<PathBuf>, SourceError),
|
Read(Option<PathBuf>, SourceError),
|
||||||
Format(Option<PathBuf>, FormatModuleError),
|
Format(Option<PathBuf>, FormatModuleError),
|
||||||
Write(Option<PathBuf>, SourceError),
|
Write(Option<PathBuf>, SourceError),
|
||||||
@@ -821,9 +821,14 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
.collect();
|
.collect();
|
||||||
rule_names.sort();
|
rule_names.sort();
|
||||||
if let [rule] = rule_names.as_slice() {
|
if let [rule] = rule_names.as_slice() {
|
||||||
warn_user_once!("The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.");
|
warn_user_once!(
|
||||||
|
"The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration."
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
|
warn_user_once!(
|
||||||
|
"The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `lint.select` or `lint.extend-select` configuration, or adding them to the `lint.ignore` configuration.",
|
||||||
|
rule_names.join(", ")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,7 +838,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
if setting.linter.rules.enabled(Rule::TabIndentation)
|
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||||
&& setting.formatter.indent_style.is_tab()
|
&& setting.formatter.indent_style.is_tab()
|
||||||
{
|
{
|
||||||
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
warn_user_once!(
|
||||||
|
"The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !setting
|
if !setting
|
||||||
@@ -846,14 +853,18 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
.enabled(Rule::MultiLineImplicitStringConcatenation)
|
.enabled(Rule::MultiLineImplicitStringConcatenation)
|
||||||
&& !setting.linter.flake8_implicit_str_concat.allow_multiline
|
&& !setting.linter.flake8_implicit_str_concat.allow_multiline
|
||||||
{
|
{
|
||||||
warn_user_once!("The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`.");
|
warn_user_once!(
|
||||||
|
"The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all rules that rely on tab styles.
|
// Validate all rules that rely on tab styles.
|
||||||
if setting.linter.rules.enabled(Rule::DocstringTabIndentation)
|
if setting.linter.rules.enabled(Rule::DocstringTabIndentation)
|
||||||
&& setting.formatter.indent_style.is_tab()
|
&& setting.formatter.indent_style.is_tab()
|
||||||
{
|
{
|
||||||
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
warn_user_once!(
|
||||||
|
"The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all rules that rely on custom indent widths.
|
// Validate all rules that rely on custom indent widths.
|
||||||
@@ -862,7 +873,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
Rule::IndentationWithInvalidMultipleComment,
|
Rule::IndentationWithInvalidMultipleComment,
|
||||||
]) && setting.formatter.indent_width.value() != 4
|
]) && setting.formatter.indent_width.value() != 4
|
||||||
{
|
{
|
||||||
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
|
warn_user_once!(
|
||||||
|
"The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all rules that rely on quote styles.
|
// Validate all rules that rely on quote styles.
|
||||||
@@ -876,10 +889,14 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
setting.formatter.quote_style,
|
setting.formatter.quote_style,
|
||||||
) {
|
) {
|
||||||
(Quote::Double, QuoteStyle::Single) => {
|
(Quote::Double, QuoteStyle::Single) => {
|
||||||
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
warn_user_once!(
|
||||||
|
"The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(Quote::Single, QuoteStyle::Double) => {
|
(Quote::Single, QuoteStyle::Double) => {
|
||||||
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
warn_user_once!(
|
||||||
|
"The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -892,7 +909,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
QuoteStyle::Single | QuoteStyle::Double
|
QuoteStyle::Single | QuoteStyle::Double
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
|
warn_user_once!(
|
||||||
|
"The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
|
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
|
||||||
@@ -902,7 +921,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
QuoteStyle::Single | QuoteStyle::Double
|
QuoteStyle::Single | QuoteStyle::Double
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
warn_user_once!("The `flake8-quotes.docstring-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
|
warn_user_once!(
|
||||||
|
"The `flake8-quotes.docstring-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all isort settings.
|
// Validate all isort settings.
|
||||||
@@ -910,12 +931,16 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
|
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
|
||||||
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
|
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
|
||||||
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
|
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
|
||||||
warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
|
warn_user_once!(
|
||||||
|
"The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
|
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
|
||||||
if setting.linter.isort.lines_between_types > 1 {
|
if setting.linter.isort.lines_between_types > 1 {
|
||||||
warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
|
warn_user_once!(
|
||||||
|
"The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
|
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
|
||||||
@@ -924,11 +949,15 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
|||||||
&& !setting.linter.isort.force_single_line
|
&& !setting.linter.isort.force_single_line
|
||||||
{
|
{
|
||||||
if setting.linter.isort.force_wrap_aliases {
|
if setting.linter.isort.force_wrap_aliases {
|
||||||
warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
|
warn_user_once!(
|
||||||
|
"The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.linter.isort.split_on_trailing_comma {
|
if setting.linter.isort.split_on_trailing_comma {
|
||||||
warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
|
warn_user_once!(
|
||||||
|
"The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ use log::error;
|
|||||||
|
|
||||||
use ruff_linter::source_kind::SourceKind;
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
use ruff_workspace::resolver::{Resolver, match_exclusion, python_file_at_path};
|
||||||
|
|
||||||
|
use crate::ExitStatus;
|
||||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||||
use crate::commands::format::{
|
use crate::commands::format::{
|
||||||
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
FormatCommandError, FormatMode, FormatResult, FormattedSource, format_source,
|
||||||
FormatResult, FormattedSource,
|
warn_incompatible_formatter_settings,
|
||||||
};
|
};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||||
use crate::ExitStatus;
|
|
||||||
|
|
||||||
/// Run the formatter over a single file, read from `stdin`.
|
/// Run the formatter over a single file, read from `stdin`.
|
||||||
pub(crate) fn format_stdin(
|
pub(crate) fn format_stdin(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ struct Explanation<'a> {
|
|||||||
summary: &'a str,
|
summary: &'a str,
|
||||||
message_formats: &'a [&'a str],
|
message_formats: &'a [&'a str],
|
||||||
fix: String,
|
fix: String,
|
||||||
#[allow(clippy::struct_field_names)]
|
#[expect(clippy::struct_field_names)]
|
||||||
explanation: Option<&'a str>,
|
explanation: Option<&'a str>,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::ConfigArguments;
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ use rustc_hash::FxHashMap;
|
|||||||
|
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_linter::codes::Rule;
|
use ruff_linter::codes::Rule;
|
||||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
|
||||||
use ruff_linter::message::{Message, SyntaxErrorMessage};
|
use ruff_linter::message::Message;
|
||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||||
use ruff_linter::settings::types::UnsafeFixes;
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{LinterSettings, flags};
|
||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::{fs, IOError};
|
use ruff_linter::{IOError, fs};
|
||||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::TextRange;
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||||
@@ -71,7 +71,7 @@ impl Diagnostics {
|
|||||||
TextRange::default(),
|
TextRange::default(),
|
||||||
),
|
),
|
||||||
source_file,
|
source_file,
|
||||||
TextSize::default(),
|
None,
|
||||||
)],
|
)],
|
||||||
FxHashMap::default(),
|
FxHashMap::default(),
|
||||||
)
|
)
|
||||||
@@ -102,11 +102,7 @@ impl Diagnostics {
|
|||||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||||
Self::new(
|
Self::new(
|
||||||
vec![Message::SyntaxError(SyntaxErrorMessage {
|
vec![Message::syntax_error(err, TextRange::default(), dummy)],
|
||||||
message: err.to_string(),
|
|
||||||
range: TextRange::default(),
|
|
||||||
file: dummy,
|
|
||||||
})],
|
|
||||||
FxHashMap::default(),
|
FxHashMap::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(clippy::print_stdout)]
|
#![allow(clippy::print_stdout)]
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, stdout, BufWriter, Write};
|
use std::io::{self, BufWriter, Write, stdout};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
@@ -11,10 +11,10 @@ use anyhow::Result;
|
|||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||||
|
|
||||||
use args::{GlobalConfigArgs, ServerCommand};
|
use args::{GlobalConfigArgs, ServerCommand};
|
||||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
use ruff_linter::logging::{LogLevel, set_up_logging};
|
||||||
use ruff_linter::settings::flags::FixMode;
|
use ruff_linter::settings::flags::FixMode;
|
||||||
use ruff_linter::settings::types::OutputFormat;
|
use ruff_linter::settings::types::OutputFormat;
|
||||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||||
@@ -134,7 +134,7 @@ pub fn run(
|
|||||||
{
|
{
|
||||||
let default_panic_hook = std::panic::take_hook();
|
let default_panic_hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
#[allow(clippy::print_stderr)]
|
#[expect(clippy::print_stderr)]
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
r#"
|
r#"
|
||||||
@@ -326,7 +326,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||||
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
||||||
let s = if modifications == 1 { "" } else { "s" };
|
let s = if modifications == 1 { "" } else { "s" };
|
||||||
#[allow(clippy::print_stderr)]
|
#[expect(clippy::print_stderr)]
|
||||||
{
|
{
|
||||||
eprintln!("Added {modifications} noqa directive{s}.");
|
eprintln!("Added {modifications} noqa directive{s}.");
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
mod test_file_change_detector {
|
mod test_file_change_detector {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{change_detected, ChangeKind};
|
use crate::{ChangeKind, change_detected};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_correct_file_change() {
|
fn detect_correct_file_change() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use clap::Parser;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use ruff::args::Args;
|
use ruff::args::Args;
|
||||||
use ruff::{run, ExitStatus};
|
use ruff::{ExitStatus, run};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@@ -15,6 +15,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|||||||
not(target_os = "windows"),
|
not(target_os = "windows"),
|
||||||
not(target_os = "openbsd"),
|
not(target_os = "openbsd"),
|
||||||
not(target_os = "aix"),
|
not(target_os = "aix"),
|
||||||
|
not(target_os = "android"),
|
||||||
any(
|
any(
|
||||||
target_arch = "x86_64",
|
target_arch = "x86_64",
|
||||||
target_arch = "aarch64",
|
target_arch = "aarch64",
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::{iterate, Itertools};
|
use itertools::{Itertools, iterate};
|
||||||
|
use ruff_linter::codes::NoqaCode;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use ruff_linter::fs::relativize_path;
|
use ruff_linter::fs::relativize_path;
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
use ruff_linter::message::{
|
use ruff_linter::message::{
|
||||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, MessageKind, PylintEmitter,
|
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, PylintEmitter, RdjsonEmitter,
|
||||||
RdjsonEmitter, SarifEmitter, TextEmitter,
|
SarifEmitter, TextEmitter,
|
||||||
};
|
};
|
||||||
use ruff_linter::notify_user;
|
use ruff_linter::notify_user;
|
||||||
use ruff_linter::registry::Rule;
|
|
||||||
use ruff_linter::settings::flags::{self};
|
use ruff_linter::settings::flags::{self};
|
||||||
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
|
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
|
||||||
|
|
||||||
@@ -37,59 +36,12 @@ bitflags! {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ExpandedStatistics {
|
struct ExpandedStatistics {
|
||||||
code: Option<SerializeRuleAsCode>,
|
code: Option<NoqaCode>,
|
||||||
name: SerializeMessageKindAsTitle,
|
name: &'static str,
|
||||||
count: usize,
|
count: usize,
|
||||||
fixable: bool,
|
fixable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct SerializeRuleAsCode(Rule);
|
|
||||||
|
|
||||||
impl Serialize for SerializeRuleAsCode {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&self.0.noqa_code().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SerializeRuleAsCode {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0.noqa_code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rule> for SerializeRuleAsCode {
|
|
||||||
fn from(rule: Rule) -> Self {
|
|
||||||
Self(rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SerializeMessageKindAsTitle(MessageKind);
|
|
||||||
|
|
||||||
impl Serialize for SerializeMessageKindAsTitle {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.0.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SerializeMessageKindAsTitle {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(self.0.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MessageKind> for SerializeMessageKindAsTitle {
|
|
||||||
fn from(kind: MessageKind) -> Self {
|
|
||||||
Self(kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Printer {
|
pub(crate) struct Printer {
|
||||||
format: OutputFormat,
|
format: OutputFormat,
|
||||||
log_level: LogLevel,
|
log_level: LogLevel,
|
||||||
@@ -157,7 +109,8 @@ impl Printer {
|
|||||||
} else {
|
} else {
|
||||||
"es"
|
"es"
|
||||||
};
|
};
|
||||||
writeln!(writer,
|
writeln!(
|
||||||
|
writer,
|
||||||
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||||
fixables.applicable, fixables.inapplicable_unsafe
|
fixables.applicable, fixables.inapplicable_unsafe
|
||||||
)?;
|
)?;
|
||||||
@@ -175,7 +128,8 @@ impl Printer {
|
|||||||
} else {
|
} else {
|
||||||
"es"
|
"es"
|
||||||
};
|
};
|
||||||
writeln!(writer,
|
writeln!(
|
||||||
|
writer,
|
||||||
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||||
fixables.inapplicable_unsafe
|
fixables.inapplicable_unsafe
|
||||||
)?;
|
)?;
|
||||||
@@ -205,15 +159,27 @@ impl Printer {
|
|||||||
if fixed > 0 {
|
if fixed > 0 {
|
||||||
let s = if fixed == 1 { "" } else { "s" };
|
let s = if fixed == 1 { "" } else { "s" };
|
||||||
if self.fix_mode.is_apply() {
|
if self.fix_mode.is_apply() {
|
||||||
writeln!(writer, "Fixed {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"Fixed {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`)."
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(writer, "Would fix {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"Would fix {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`)."
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.fix_mode.is_apply() {
|
if self.fix_mode.is_apply() {
|
||||||
writeln!(writer, "No errors fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"No errors fixed ({unapplied} fix{es} available with `--unsafe-fixes`)."
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(writer, "No errors would be fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"No errors would be fixed ({unapplied} fix{es} available with `--unsafe-fixes`)."
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -241,7 +207,6 @@ impl Printer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||||
#[allow(deprecated)]
|
|
||||||
if matches!(
|
if matches!(
|
||||||
self.format,
|
self.format,
|
||||||
OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped
|
OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped
|
||||||
@@ -337,21 +302,25 @@ impl Printer {
|
|||||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||||
.messages
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
.sorted_by_key(|message| (message.rule(), message.fixable()))
|
.map(|message| (message.to_noqa_code(), message))
|
||||||
.fold(vec![], |mut acc: Vec<(&Message, usize)>, message| {
|
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
||||||
if let Some((prev_message, count)) = acc.last_mut() {
|
.fold(
|
||||||
if prev_message.rule() == message.rule() {
|
vec![],
|
||||||
*count += 1;
|
|mut acc: Vec<((Option<NoqaCode>, &Message), usize)>, (code, message)| {
|
||||||
return acc;
|
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
||||||
|
if *prev_code == code {
|
||||||
|
*count += 1;
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
acc.push(((code, message), 1));
|
||||||
acc.push((message, 1));
|
acc
|
||||||
acc
|
},
|
||||||
})
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(message, count)| ExpandedStatistics {
|
.map(|&((code, message), count)| ExpandedStatistics {
|
||||||
code: message.rule().map(std::convert::Into::into),
|
code,
|
||||||
name: message.kind().into(),
|
name: message.name(),
|
||||||
count,
|
count,
|
||||||
fixable: if let Some(fix) = message.fix() {
|
fixable: if let Some(fix) = message.fix() {
|
||||||
fix.applies(self.unsafe_fixes.required_applicability())
|
fix.applies(self.unsafe_fixes.required_applicability())
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
|
|
||||||
use ruff_workspace::configuration::Configuration;
|
use ruff_workspace::configuration::Configuration;
|
||||||
use ruff_workspace::pyproject::{self, find_fallback_target_version};
|
use ruff_workspace::pyproject::{self, find_fallback_target_version};
|
||||||
use ruff_workspace::resolver::{
|
use ruff_workspace::resolver::{
|
||||||
resolve_root_settings, ConfigurationOrigin, ConfigurationTransformer, PyprojectConfig,
|
ConfigurationOrigin, ConfigurationTransformer, PyprojectConfig, PyprojectDiscoveryStrategy,
|
||||||
PyprojectDiscoveryStrategy,
|
resolve_root_settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|||||||
@@ -422,3 +422,153 @@ fn nested_imports() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for venv resolution with the `--python` flag.
|
||||||
|
///
|
||||||
|
/// Based on the [albatross-virtual-workspace] example from the uv repo and the report in [#16598].
|
||||||
|
///
|
||||||
|
/// [albatross-virtual-workspace]: https://github.com/astral-sh/uv/tree/aa629c4a/scripts/workspaces/albatross-virtual-workspace
|
||||||
|
/// [#16598]: https://github.com/astral-sh/ruff/issues/16598
|
||||||
|
#[test]
|
||||||
|
fn venv() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let root = ChildPath::new(tempdir.path());
|
||||||
|
|
||||||
|
// packages
|
||||||
|
// ├── albatross
|
||||||
|
// │ ├── check_installed_albatross.py
|
||||||
|
// │ ├── pyproject.toml
|
||||||
|
// │ └── src
|
||||||
|
// │ └── albatross
|
||||||
|
// │ └── __init__.py
|
||||||
|
// └── bird-feeder
|
||||||
|
// ├── check_installed_bird_feeder.py
|
||||||
|
// ├── pyproject.toml
|
||||||
|
// └── src
|
||||||
|
// └── bird_feeder
|
||||||
|
// └── __init__.py
|
||||||
|
|
||||||
|
let packages = root.child("packages");
|
||||||
|
|
||||||
|
let albatross = packages.child("albatross");
|
||||||
|
albatross
|
||||||
|
.child("check_installed_albatross.py")
|
||||||
|
.write_str("from albatross import fly")?;
|
||||||
|
albatross
|
||||||
|
.child("pyproject.toml")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "albatross"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["bird-feeder", "tqdm>=4,<5"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
bird-feeder = { workspace = true }
|
||||||
|
"#})?;
|
||||||
|
albatross
|
||||||
|
.child("src")
|
||||||
|
.child("albatross")
|
||||||
|
.child("__init__.py")
|
||||||
|
.write_str("import tqdm; from bird_feeder import use")?;
|
||||||
|
|
||||||
|
let bird_feeder = packages.child("bird-feeder");
|
||||||
|
bird_feeder
|
||||||
|
.child("check_installed_bird_feeder.py")
|
||||||
|
.write_str("from bird_feeder import use; from albatross import fly")?;
|
||||||
|
bird_feeder
|
||||||
|
.child("pyproject.toml")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "bird-feeder"
|
||||||
|
version = "1.0.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio>=4.3.0,<5"]
|
||||||
|
"#})?;
|
||||||
|
bird_feeder
|
||||||
|
.child("src")
|
||||||
|
.child("bird_feeder")
|
||||||
|
.child("__init__.py")
|
||||||
|
.write_str("import anyio")?;
|
||||||
|
|
||||||
|
let venv = root.child(".venv");
|
||||||
|
let bin = venv.child("bin");
|
||||||
|
bin.child("python").touch()?;
|
||||||
|
let home = format!("home = {}", bin.to_string_lossy());
|
||||||
|
venv.child("pyvenv.cfg").write_str(&home)?;
|
||||||
|
let site_packages = venv.child("lib").child("python3.12").child("site-packages");
|
||||||
|
site_packages
|
||||||
|
.child("_albatross.pth")
|
||||||
|
.write_str(&albatross.join("src").to_string_lossy())?;
|
||||||
|
site_packages
|
||||||
|
.child("_bird_feeder.pth")
|
||||||
|
.write_str(&bird_feeder.join("src").to_string_lossy())?;
|
||||||
|
site_packages.child("tqdm").child("__init__.py").touch()?;
|
||||||
|
|
||||||
|
// without `--python .venv`, the result should only include dependencies within the albatross
|
||||||
|
// package
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().arg("packages/albatross").current_dir(&root),
|
||||||
|
@r#"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"packages/albatross/check_installed_albatross.py": [
|
||||||
|
"packages/albatross/src/albatross/__init__.py"
|
||||||
|
],
|
||||||
|
"packages/albatross/src/albatross/__init__.py": []
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// with `--python .venv` both workspace and third-party dependencies are included
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().args(["--python", ".venv"]).arg("packages/albatross").current_dir(&root),
|
||||||
|
@r#"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"packages/albatross/check_installed_albatross.py": [
|
||||||
|
"packages/albatross/src/albatross/__init__.py"
|
||||||
|
],
|
||||||
|
"packages/albatross/src/albatross/__init__.py": [
|
||||||
|
".venv/lib/python3.12/site-packages/tqdm/__init__.py",
|
||||||
|
"packages/bird-feeder/src/bird_feeder/__init__.py"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// test the error message for a non-existent venv. it's important that the `ruff analyze graph`
|
||||||
|
// flag matches the ty flag used to generate the error message (`--python`)
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ruff failed
|
||||||
|
Cause: Invalid search path settings
|
||||||
|
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` could not be canonicalized
|
||||||
|
");
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -862,7 +862,7 @@ if condition:
|
|||||||
print('Should change quotes')
|
print('Should change quotes')
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||||
"#);
|
"#);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -999,7 +999,7 @@ def say_hy(name: str):
|
|||||||
1 file reformatted
|
1 file reformatted
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
warning: The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`.
|
warning: The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`.
|
||||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
@@ -1059,7 +1059,7 @@ def say_hy(name: str):
|
|||||||
print(f"Hy {name}")
|
print(f"Hy {name}")
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||||
@@ -1199,7 +1199,7 @@ def say_hy(name: str):
|
|||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
|
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
|
||||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||||
");
|
");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -994,6 +994,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
|
|||||||
- `lint.extend-per-file-ignores`
|
- `lint.extend-per-file-ignores`
|
||||||
- `lint.exclude`
|
- `lint.exclude`
|
||||||
- `lint.preview`
|
- `lint.preview`
|
||||||
|
- `lint.typing-extensions`
|
||||||
|
|
||||||
For more information, try '--help'.
|
For more information, try '--help'.
|
||||||
");
|
");
|
||||||
@@ -1156,18 +1157,20 @@ include = ["*.ipy"]
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn warn_invalid_noqa_with_no_diagnostics() {
|
fn warn_invalid_noqa_with_no_diagnostics() {
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(
|
||||||
.args(STDIN_BASE_OPTIONS)
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(["--isolated"])
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--select")
|
.args(["--isolated"])
|
||||||
.arg("F401")
|
.arg("--select")
|
||||||
.arg("-")
|
.arg("F401")
|
||||||
.pass_stdin(
|
.arg("-")
|
||||||
r#"
|
.pass_stdin(
|
||||||
|
r#"
|
||||||
# ruff: noqa: AAA101
|
# ruff: noqa: AAA101
|
||||||
print("Hello world!")
|
print("Hello world!")
|
||||||
"#
|
"#
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1899,6 +1902,40 @@ def first_square():
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test for <https://github.com/astral-sh/ruff/issues/2253>
|
||||||
|
#[test]
|
||||||
|
fn add_noqa_parent() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let test_path = tempdir.path().join("noqa.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
from foo import ( # noqa: F401
|
||||||
|
bar
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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("--add-noqa")
|
||||||
|
.arg("--select=F401")
|
||||||
|
.arg("noqa.py")
|
||||||
|
.current_dir(&tempdir), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||||
#[test]
|
#[test]
|
||||||
fn requires_python() -> Result<()> {
|
fn requires_python() -> Result<()> {
|
||||||
@@ -2117,7 +2154,7 @@ requires-python = ">= 3.11"
|
|||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.current_dir(project_dir)
|
.current_dir(project_dir)
|
||||||
, @r###"
|
, @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -2207,6 +2244,7 @@ requires-python = ">= 3.11"
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -2390,7 +2428,7 @@ requires-python = ">= 3.11"
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2428,7 +2466,7 @@ requires-python = ">= 3.11"
|
|||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.current_dir(project_dir)
|
.current_dir(project_dir)
|
||||||
, @r###"
|
, @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -2518,6 +2556,7 @@ requires-python = ">= 3.11"
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -2701,7 +2740,7 @@ requires-python = ">= 3.11"
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2790,7 +2829,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.current_dir(project_dir), @r###"
|
.current_dir(project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -2881,6 +2920,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -3064,7 +3104,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3170,7 +3210,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.args(["--select","UP007"])
|
.args(["--select","UP007"])
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -3260,6 +3300,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -3443,7 +3484,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3475,7 +3516,7 @@ requires-python = ">= 3.11"
|
|||||||
&inner_pyproject,
|
&inner_pyproject,
|
||||||
r#"
|
r#"
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -3497,7 +3538,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.args(["--select","UP007"])
|
.args(["--select","UP007"])
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -3587,6 +3628,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -3770,7 +3812,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3823,7 +3865,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -3890,7 +3932,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
linter.per_file_ignores = {}
|
linter.per_file_ignores = {}
|
||||||
linter.safety_table.forced_safe = []
|
linter.safety_table.forced_safe = []
|
||||||
linter.safety_table.forced_unsafe = []
|
linter.safety_table.forced_unsafe = []
|
||||||
linter.unresolved_target_version = 3.9
|
linter.unresolved_target_version = none
|
||||||
linter.per_file_target_version = {}
|
linter.per_file_target_version = {}
|
||||||
linter.preview = disabled
|
linter.preview = disabled
|
||||||
linter.explicit_preview_rules = false
|
linter.explicit_preview_rules = false
|
||||||
@@ -3914,6 +3956,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -4097,7 +4140,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
|
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
@@ -4107,7 +4150,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.current_dir(project_dir.join("foo")), @r###"
|
.current_dir(project_dir.join("foo")), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -4174,7 +4217,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
linter.per_file_ignores = {}
|
linter.per_file_ignores = {}
|
||||||
linter.safety_table.forced_safe = []
|
linter.safety_table.forced_safe = []
|
||||||
linter.safety_table.forced_unsafe = []
|
linter.safety_table.forced_unsafe = []
|
||||||
linter.unresolved_target_version = 3.9
|
linter.unresolved_target_version = none
|
||||||
linter.per_file_target_version = {}
|
linter.per_file_target_version = {}
|
||||||
linter.preview = disabled
|
linter.preview = disabled
|
||||||
linter.explicit_preview_rules = false
|
linter.explicit_preview_rules = false
|
||||||
@@ -4198,6 +4241,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -4381,7 +4425,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -4444,7 +4488,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
@@ -4535,6 +4579,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
@@ -4718,7 +4763,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -4954,30 +4999,81 @@ fn flake8_import_convention_invalid_aliases_config_module_name() -> Result<()> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flake8_import_convention_unused_aliased_import() {
|
fn flake8_import_convention_unused_aliased_import() {
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(
|
||||||
.args(STDIN_BASE_OPTIONS)
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.arg("--config")
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg(r#"lint.isort.required-imports = ["import pandas"]"#)
|
.arg("--config")
|
||||||
.args(["--select", "I002,ICN001,F401"])
|
.arg(r#"lint.isort.required-imports = ["import pandas"]"#)
|
||||||
.args(["--stdin-filename", "test.py"])
|
.args(["--select", "I002,ICN001,F401"])
|
||||||
.arg("--unsafe-fixes")
|
.args(["--stdin-filename", "test.py"])
|
||||||
.arg("--fix")
|
.arg("--unsafe-fixes")
|
||||||
.arg("-")
|
.arg("--fix")
|
||||||
.pass_stdin("1"));
|
.arg("-")
|
||||||
|
.pass_stdin("1")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flake8_import_convention_unused_aliased_import_no_conflict() {
|
fn flake8_import_convention_unused_aliased_import_no_conflict() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(r#"lint.isort.required-imports = ["import pandas as pd"]"#)
|
||||||
|
.args(["--select", "I002,ICN001,F401"])
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("--unsafe-fixes")
|
||||||
|
.arg("--fix")
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin("1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See: https://github.com/astral-sh/ruff/issues/16177
|
||||||
|
#[test]
|
||||||
|
fn flake8_pyi_redundant_none_literal() {
|
||||||
|
let snippet = r#"
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# For each of these expressions, Ruff provides a fix for one of the `Literal[None]` elements
|
||||||
|
# but not both, as if both were autofixed it would result in `None | None`,
|
||||||
|
# which leads to a `TypeError` at runtime.
|
||||||
|
a: Literal[None,] | Literal[None,]
|
||||||
|
b: Literal[None] | Literal[None]
|
||||||
|
c: Literal[None] | Literal[None,]
|
||||||
|
d: Literal[None,] | Literal[None]
|
||||||
|
"#;
|
||||||
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.args(["--select", "PYI061"])
|
||||||
.arg(r#"lint.isort.required-imports = ["import pandas as pd"]"#)
|
|
||||||
.args(["--select", "I002,ICN001,F401"])
|
|
||||||
.args(["--stdin-filename", "test.py"])
|
.args(["--stdin-filename", "test.py"])
|
||||||
.arg("--unsafe-fixes")
|
.arg("--preview")
|
||||||
.arg("--fix")
|
.arg("--diff")
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin("1"));
|
.pass_stdin(snippet), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- test.py
|
||||||
|
+++ test.py
|
||||||
|
@@ -4,7 +4,7 @@
|
||||||
|
# For each of these expressions, Ruff provides a fix for one of the `Literal[None]` elements
|
||||||
|
# but not both, as if both were autofixed it would result in `None | None`,
|
||||||
|
# which leads to a `TypeError` at runtime.
|
||||||
|
-a: Literal[None,] | Literal[None,]
|
||||||
|
-b: Literal[None] | Literal[None]
|
||||||
|
-c: Literal[None] | Literal[None,]
|
||||||
|
-d: Literal[None,] | Literal[None]
|
||||||
|
+a: None | Literal[None,]
|
||||||
|
+b: None | Literal[None]
|
||||||
|
+c: None | Literal[None,]
|
||||||
|
+d: None | Literal[None]
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Would fix 4 errors.
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that private, old-style `TypeVar` generics
|
/// Test that private, old-style `TypeVar` generics
|
||||||
@@ -5175,8 +5271,8 @@ fn a005_module_shadowing_non_strict() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Test A005 with `strict-checking` unset
|
/// Test A005 with `strict-checking` unset
|
||||||
/// TODO(brent) This should currently match the strict version, but after the next minor
|
///
|
||||||
/// release it will match the non-strict version directly above
|
/// This should match the non-strict version directly above
|
||||||
#[test]
|
#[test]
|
||||||
fn a005_module_shadowing_strict_default() -> Result<()> {
|
fn a005_module_shadowing_strict_default() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
@@ -5564,3 +5660,34 @@ fn semantic_syntax_errors() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test for <https://github.com/astral-sh/ruff/issues/17821>.
|
||||||
|
///
|
||||||
|
/// `lint.typing-extensions = false` with Python 3.9 should disable the PYI019 lint because it would
|
||||||
|
/// try to import `Self` from `typing_extensions`
|
||||||
|
#[test]
|
||||||
|
fn combine_typing_extensions_config() {
|
||||||
|
let contents = "
|
||||||
|
from typing import TypeVar
|
||||||
|
T = TypeVar('T')
|
||||||
|
class Foo:
|
||||||
|
def f(self: T) -> T: ...
|
||||||
|
";
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--config", "lint.typing-extensions = false"])
|
||||||
|
.arg("--select=PYI019")
|
||||||
|
.arg("--target-version=py39")
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(contents),
|
||||||
|
@r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ info:
|
|||||||
args:
|
args:
|
||||||
- rule
|
- rule
|
||||||
- F401
|
- F401
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
@@ -84,6 +83,11 @@ else:
|
|||||||
print("numpy is not installed")
|
print("numpy is not installed")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
|
||||||
|
the criterion for determining whether an import is first-party
|
||||||
|
is stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
- `lint.ignore-init-module-imports`
|
- `lint.ignore-init-module-imports`
|
||||||
- `lint.pyflakes.allowed-unused-imports`
|
- `lint.pyflakes.allowed-unused-imports`
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ linter.task_tags = [
|
|||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
//!
|
//!
|
||||||
//! The above snippet has been built out of the following structure:
|
//! The above snippet has been built out of the following structure:
|
||||||
use crate::snippet;
|
use crate::snippet;
|
||||||
use std::cmp::{max, min, Reverse};
|
use std::cmp::{Reverse, max, min};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
@@ -41,7 +41,7 @@ use std::{cmp, fmt};
|
|||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::renderer::styled_buffer::StyledBuffer;
|
use crate::renderer::styled_buffer::StyledBuffer;
|
||||||
use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};
|
use crate::renderer::{DEFAULT_TERM_WIDTH, Margin, Style, stylesheet::Stylesheet};
|
||||||
|
|
||||||
const ANONYMIZED_LINE_NUM: &str = "LL";
|
const ANONYMIZED_LINE_NUM: &str = "LL";
|
||||||
const ERROR_TXT: &str = "error";
|
const ERROR_TXT: &str = "error";
|
||||||
@@ -1273,10 +1273,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
|
|||||||
let inline_marks = lines
|
let inline_marks = lines
|
||||||
.last()
|
.last()
|
||||||
.and_then(|line| {
|
.and_then(|line| {
|
||||||
if let DisplayLine::Source {
|
if let DisplayLine::Source { inline_marks, .. } = line {
|
||||||
ref inline_marks, ..
|
|
||||||
} = line
|
|
||||||
{
|
|
||||||
let inline_marks = inline_marks.clone();
|
let inline_marks = inline_marks.clone();
|
||||||
Some(inline_marks)
|
Some(inline_marks)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ mod deserialize;
|
|||||||
|
|
||||||
use crate::deserialize::Fixture;
|
use crate::deserialize::Fixture;
|
||||||
use ruff_annotate_snippets::{Message, Renderer};
|
use ruff_annotate_snippets::{Message, Renderer};
|
||||||
use snapbox::data::DataFormat;
|
|
||||||
use snapbox::Data;
|
use snapbox::Data;
|
||||||
|
use snapbox::data::DataFormat;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ name = "formatter"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "red_knot"
|
name = "ty"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -49,7 +49,7 @@ ruff_python_ast = { workspace = true }
|
|||||||
ruff_python_formatter = { workspace = true }
|
ruff_python_formatter = { workspace = true }
|
||||||
ruff_python_parser = { workspace = true }
|
ruff_python_parser = { workspace = true }
|
||||||
ruff_python_trivia = { workspace = true }
|
ruff_python_trivia = { workspace = true }
|
||||||
red_knot_project = { workspace = true }
|
ty_project = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use ruff_benchmark::criterion::{
|
use ruff_benchmark::criterion::{
|
||||||
criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
BenchmarkId, Criterion, Throughput, criterion_group, criterion_main,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ruff_benchmark::{
|
use ruff_benchmark::{
|
||||||
TestCase, LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, UNICODE_PYPINYIN,
|
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||||
};
|
};
|
||||||
use ruff_python_formatter::{format_module_ast, PreviewMode, PyFormatOptions};
|
use ruff_python_formatter::{PreviewMode, PyFormatOptions, format_module_ast};
|
||||||
use ruff_python_parser::{parse, Mode, ParseOptions};
|
use ruff_python_parser::{Mode, ParseOptions, parse};
|
||||||
use ruff_python_trivia::CommentRanges;
|
use ruff_python_trivia::CommentRanges;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use ruff_benchmark::criterion;
|
use ruff_benchmark::criterion;
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{
|
||||||
criterion_group, criterion_main, measurement::WallTime, BenchmarkId, Criterion, Throughput,
|
BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime,
|
||||||
};
|
};
|
||||||
use ruff_benchmark::{
|
use ruff_benchmark::{
|
||||||
TestCase, LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, UNICODE_PYPINYIN,
|
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||||
};
|
};
|
||||||
use ruff_python_parser::{lexer, Mode, TokenKind};
|
use ruff_python_parser::{Mode, TokenKind, lexer};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
use ruff_benchmark::criterion;
|
use ruff_benchmark::criterion;
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{
|
||||||
criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
|
BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main,
|
||||||
};
|
};
|
||||||
use ruff_benchmark::{
|
use ruff_benchmark::{
|
||||||
TestCase, LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, UNICODE_PYPINYIN,
|
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||||
};
|
};
|
||||||
use ruff_linter::linter::{lint_only, ParseSource};
|
use ruff_linter::linter::{ParseSource, lint_only};
|
||||||
use ruff_linter::rule_selector::PreviewOptions;
|
use ruff_linter::rule_selector::PreviewOptions;
|
||||||
use ruff_linter::settings::rule_table::RuleTable;
|
use ruff_linter::settings::rule_table::RuleTable;
|
||||||
use ruff_linter::settings::types::PreviewMode;
|
use ruff_linter::settings::types::PreviewMode;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{LinterSettings, flags};
|
||||||
use ruff_linter::source_kind::SourceKind;
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_linter::{registry::Rule, RuleSelector};
|
use ruff_linter::{RuleSelector, registry::Rule};
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::PySourceType;
|
||||||
use ruff_python_parser::parse_module;
|
use ruff_python_parser::parse_module;
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
|||||||
target_arch = "powerpc64"
|
target_arch = "powerpc64"
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
#[allow(non_upper_case_globals)]
|
#[unsafe(export_name = "_rjem_malloc_conf")]
|
||||||
#[export_name = "_rjem_malloc_conf"]
|
#[expect(non_upper_case_globals)]
|
||||||
#[allow(unsafe_code)]
|
#[expect(unsafe_code)]
|
||||||
pub static _rjem_malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";
|
pub static _rjem_malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";
|
||||||
|
|
||||||
fn create_test_cases() -> Vec<TestCase> {
|
fn create_test_cases() -> Vec<TestCase> {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use ruff_benchmark::criterion;
|
use ruff_benchmark::criterion;
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{
|
||||||
criterion_group, criterion_main, measurement::WallTime, BenchmarkId, Criterion, Throughput,
|
BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime,
|
||||||
};
|
};
|
||||||
use ruff_benchmark::{
|
use ruff_benchmark::{
|
||||||
TestCase, LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, UNICODE_PYPINYIN,
|
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||||
};
|
};
|
||||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
|
||||||
use ruff_python_ast::Stmt;
|
use ruff_python_ast::Stmt;
|
||||||
|
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
||||||
use ruff_python_parser::parse_module;
|
use ruff_python_parser::parse_module;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user