Compare commits
332 Commits
alex/submo
...
zb/fix-sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a2f78786a | ||
|
|
7b6d0a90bc | ||
|
|
07d352741a | ||
|
|
e177cc2a5a | ||
|
|
30ce679b9a | ||
|
|
76854fdb15 | ||
|
|
5a2d3cda3d | ||
|
|
fa57253980 | ||
|
|
b7fbd986bc | ||
|
|
3d334a313e | ||
|
|
2e44a861cb | ||
|
|
45bbb4cbff | ||
|
|
42b972753a | ||
|
|
f7ec178400 | ||
|
|
c315164732 | ||
|
|
bb1955e98c | ||
|
|
070e08a043 | ||
|
|
bab3924833 | ||
|
|
10748b2fdb | ||
|
|
56539db520 | ||
|
|
8d32ad1cab | ||
|
|
b2a8c42b51 | ||
|
|
7bb5dd87ff | ||
|
|
06305f3c02 | ||
|
|
9cc132f098 | ||
|
|
cf8d2e35a8 | ||
|
|
0290f5dc3b | ||
|
|
5bb9ee2a9d | ||
|
|
638f230910 | ||
|
|
b36ff75a24 | ||
|
|
30c3f9aafe | ||
|
|
883701ae88 | ||
|
|
0bd7a94c27 | ||
|
|
421f88bb32 | ||
|
|
b0eb39d112 | ||
|
|
260f463edd | ||
|
|
52849a5e68 | ||
|
|
2a61fe2353 | ||
|
|
764ad8b29b | ||
|
|
85af715880 | ||
|
|
b0bc990cbf | ||
|
|
ad3de4e488 | ||
|
|
2214a46139 | ||
|
|
c02bd11b93 | ||
|
|
eeaaa8e9fe | ||
|
|
7f7485d608 | ||
|
|
d755f3b522 | ||
|
|
83168a1bb1 | ||
|
|
0f373603eb | ||
|
|
cc23af944f | ||
|
|
0589700ca1 | ||
|
|
43d983ecae | ||
|
|
5c69bb564c | ||
|
|
89fed85a8d | ||
|
|
051f6896ac | ||
|
|
5b1d3ac9b9 | ||
|
|
b2b0ad38ea | ||
|
|
01c0a3e960 | ||
|
|
5c942119f8 | ||
|
|
2acf1cc0fd | ||
|
|
4fdbe26445 | ||
|
|
682d29c256 | ||
|
|
8e13765b57 | ||
|
|
7d3b7c5754 | ||
|
|
d6a5bbd91c | ||
|
|
1df6544ad8 | ||
|
|
4e1cf5747a | ||
|
|
cbfecfaf41 | ||
|
|
8f530a7ab0 | ||
|
|
5372bb3440 | ||
|
|
d08e414179 | ||
|
|
0b918ae4d5 | ||
|
|
9838f81baf | ||
|
|
ba47349c2e | ||
|
|
04f9949711 | ||
|
|
8bc753b842 | ||
|
|
c7eea1f2e3 | ||
|
|
be8eb92946 | ||
|
|
a544c59186 | ||
|
|
bb464ed924 | ||
|
|
f57917becd | ||
|
|
82a7598aa8 | ||
|
|
e2ec2bc306 | ||
|
|
b413a6dec4 | ||
|
|
e19c050386 | ||
|
|
5a2aba237b | ||
|
|
ca5f099481 | ||
|
|
a722df6a73 | ||
|
|
dec4154c8a | ||
|
|
69d1bfbebc | ||
|
|
90b29c9e87 | ||
|
|
0ebdebddd8 | ||
|
|
d5546508cf | ||
|
|
3ac58b47bd | ||
|
|
a2b138e789 | ||
|
|
ff0ed4e752 | ||
|
|
bc8efa2fd8 | ||
|
|
4249736d74 | ||
|
|
0181568fb5 | ||
|
|
8cc7c993de | ||
|
|
315bf80eed | ||
|
|
0138cd238a | ||
|
|
5e42926eee | ||
|
|
ddb7645e9d | ||
|
|
3f63ea4b50 | ||
|
|
c8851ecf70 | ||
|
|
d442433e93 | ||
|
|
c055d665ef | ||
|
|
7a578ce833 | ||
|
|
34f7a04ef7 | ||
|
|
c9fe4e2703 | ||
|
|
fbeeb050af | ||
|
|
4fdb4e8219 | ||
|
|
c548ef2027 | ||
|
|
5a9d6a91ea | ||
|
|
c9155d5e72 | ||
|
|
8647844572 | ||
|
|
1dcb7f89f1 | ||
|
|
c1c45a6a13 | ||
|
|
c51727708a | ||
|
|
27912d46b1 | ||
|
|
71540c03b6 | ||
|
|
aa27925e87 | ||
|
|
5c320990f7 | ||
|
|
24ed28e314 | ||
|
|
2d0681da08 | ||
|
|
29bf2cd201 | ||
|
|
1b44d7e2a7 | ||
|
|
a2fb2ee06c | ||
|
|
3e00221a6c | ||
|
|
5dc0079e78 | ||
|
|
f7528bd325 | ||
|
|
59b92b3522 | ||
|
|
9ceec359a0 | ||
|
|
2dd412c89a | ||
|
|
951766d1fb | ||
|
|
7bf50e70a7 | ||
|
|
ff7086d9ad | ||
|
|
d2aabeaaa2 | ||
|
|
8293afe2ae | ||
|
|
aaadf16b1b | ||
|
|
c343e94ac5 | ||
|
|
270b8d1d14 | ||
|
|
f3714fd3c1 | ||
|
|
a9be810c38 | ||
|
|
0bec5c0362 | ||
|
|
9490fbf1e1 | ||
|
|
8727a7b179 | ||
|
|
4e4d018344 | ||
|
|
a9899af98a | ||
|
|
aea2bc2308 | ||
|
|
c35bf8f441 | ||
|
|
426125f5c0 | ||
|
|
a0b18bc153 | ||
|
|
11901384b4 | ||
|
|
dc2f0a86fd | ||
|
|
4e67a219bb | ||
|
|
8ea18966cf | ||
|
|
e548ce1ca9 | ||
|
|
eac8a90cc4 | ||
|
|
2d3466eccf | ||
|
|
45fb3732a4 | ||
|
|
0ab8521171 | ||
|
|
0ccd84136a | ||
|
|
3981a23ee9 | ||
|
|
385dd2770b | ||
|
|
7519f6c27b | ||
|
|
4686111681 | ||
|
|
4364ffbdd3 | ||
|
|
b845e81c4a | ||
|
|
c99e10eedc | ||
|
|
a364195335 | ||
|
|
dfd6ed0524 | ||
|
|
ac882f7e63 | ||
|
|
857fd4f683 | ||
|
|
285d6410d3 | ||
|
|
cbff09b9af | ||
|
|
6e0e49eda8 | ||
|
|
ef45c97dab | ||
|
|
9714c589e1 | ||
|
|
b2fb421ddd | ||
|
|
2f05ffa2c8 | ||
|
|
b623189560 | ||
|
|
f29436ca9e | ||
|
|
e42cdf8495 | ||
|
|
71a7a03ad4 | ||
|
|
48f7f42784 | ||
|
|
3deb7e1b90 | ||
|
|
5df8a959f5 | ||
|
|
6f03afe318 | ||
|
|
1951f1bbb8 | ||
|
|
10de342991 | ||
|
|
3511b7a06b | ||
|
|
f3e5713d90 | ||
|
|
a9de6b5c3e | ||
|
|
06415b1877 | ||
|
|
518d11b33f | ||
|
|
da94b99248 | ||
|
|
3c2cf49f60 | ||
|
|
fdcb5a7e73 | ||
|
|
6a025d1925 | ||
|
|
f054e7edf8 | ||
|
|
e154efa229 | ||
|
|
32f400a457 | ||
|
|
2a38395bc8 | ||
|
|
8c72b296c9 | ||
|
|
086f1e0b89 | ||
|
|
5da45f8ec7 | ||
|
|
62f20b1e86 | ||
|
|
cccb0bbaa4 | ||
|
|
9d4f1c6ae2 | ||
|
|
326025d45f | ||
|
|
3aefe85b32 | ||
|
|
b8ecc83a54 | ||
|
|
6491932757 | ||
|
|
a9f2bb41bd | ||
|
|
e2b72fbf99 | ||
|
|
14fce0d440 | ||
|
|
8ebecb2a88 | ||
|
|
45ac30a4d7 | ||
|
|
0280949000 | ||
|
|
c722f498fe | ||
|
|
1f4f8d9950 | ||
|
|
4488e9d47d | ||
|
|
b08f0b2caa | ||
|
|
d6e472f297 | ||
|
|
45842cc034 | ||
|
|
cd079bd92e | ||
|
|
5756b3809c | ||
|
|
92c5f62ec0 | ||
|
|
21e5a57296 | ||
|
|
f4e4229683 | ||
|
|
e6ddeed386 | ||
|
|
c5b8d551df | ||
|
|
f68080b55e | ||
|
|
abaa49f552 | ||
|
|
7b0aab1696 | ||
|
|
2250fa6f98 | ||
|
|
392a8e4e50 | ||
|
|
515de2d062 | ||
|
|
508c0a0861 | ||
|
|
0d2792517d | ||
|
|
05d053376b | ||
|
|
ac2552b11b | ||
|
|
644096ea8a | ||
|
|
015ab9e576 | ||
|
|
cf4196466c | ||
|
|
2182c750db | ||
|
|
72304b01eb | ||
|
|
ec854c7199 | ||
|
|
edc6ed5077 | ||
|
|
f052bd644c | ||
|
|
bc44dc2afb | ||
|
|
52f59c5c39 | ||
|
|
53299cbff4 | ||
|
|
3738ab1c46 | ||
|
|
b4f618e180 | ||
|
|
a561e6659d | ||
|
|
0e651b50b7 | ||
|
|
116fd7c7af | ||
|
|
5358ddae88 | ||
|
|
3a11e714c6 | ||
|
|
a2096ee2cb | ||
|
|
2e229aa8cb | ||
|
|
c2773b4c6f | ||
|
|
bc6517a807 | ||
|
|
4686c36079 | ||
|
|
a6cbc138d2 | ||
|
|
846df40a6e | ||
|
|
c61e885527 | ||
|
|
13af584428 | ||
|
|
984480a586 | ||
|
|
aef056954b | ||
|
|
5265af4eee | ||
|
|
5b32908920 | ||
|
|
d8d1464d96 | ||
|
|
e7beb7e1f4 | ||
|
|
b02e8212c9 | ||
|
|
69ace00210 | ||
|
|
d40590c8f9 | ||
|
|
b2387f4eab | ||
|
|
8795d9f0cb | ||
|
|
ecab623fb2 | ||
|
|
42f152108a | ||
|
|
594b7b04d3 | ||
|
|
b5b4917d7f | ||
|
|
0084e94f78 | ||
|
|
566c959add | ||
|
|
8bcfc198b8 | ||
|
|
c534bfaf01 | ||
|
|
5e1b2eef57 | ||
|
|
98681b9356 | ||
|
|
3ed537e9f1 | ||
|
|
53efc82989 | ||
|
|
666f488f1b | ||
|
|
aef2fad0c5 | ||
|
|
df66946b89 | ||
|
|
efb23b01af | ||
|
|
e5818d89fd | ||
|
|
a7d48ffd40 | ||
|
|
77f8fa6906 | ||
|
|
7c7f8d1a17 | ||
|
|
761031f729 | ||
|
|
792ec3e96e | ||
|
|
c7107a5a90 | ||
|
|
e0f3a064b9 | ||
|
|
e2e21508dc | ||
|
|
2c0c5ff4e7 | ||
|
|
adf4f1e3f4 | ||
|
|
3dbbb76654 | ||
|
|
b72120f7ef | ||
|
|
33713a7e2a | ||
|
|
5364256190 | ||
|
|
81c97e9e94 | ||
|
|
294f863523 | ||
|
|
4628180fac | ||
|
|
de32247f30 | ||
|
|
209ea06592 | ||
|
|
88bfc32dfc | ||
|
|
66d233134f | ||
|
|
15cb41c1f9 | ||
|
|
eddb9ad38d | ||
|
|
b19ddca69b | ||
|
|
747c39a26a | ||
|
|
dd15656deb | ||
|
|
adf095e889 | ||
|
|
bfd65c4215 | ||
|
|
0631e72187 | ||
|
|
bab688b76c | ||
|
|
7e277667d1 | ||
|
|
d379f3826f | ||
|
|
6f9265d78d |
@@ -7,10 +7,6 @@ serial = { max-threads = 1 }
|
|||||||
filter = 'binary(file_watching)'
|
filter = 'binary(file_watching)'
|
||||||
test-group = 'serial'
|
test-group = 'serial'
|
||||||
|
|
||||||
[[profile.default.overrides]]
|
|
||||||
filter = 'binary(e2e)'
|
|
||||||
test-group = 'serial'
|
|
||||||
|
|
||||||
[profile.ci]
|
[profile.ci]
|
||||||
# Print out output for failing tests as soon as they fail, and also at the end
|
# Print out output for failing tests as soon as they fail, and also at the end
|
||||||
# of the run (for easy scrollability).
|
# of the run (for easy scrollability).
|
||||||
|
|||||||
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -4,5 +4,6 @@
|
|||||||
# Enable off-by-default rules.
|
# Enable off-by-default rules.
|
||||||
[rules]
|
[rules]
|
||||||
possibly-unresolved-reference = "warn"
|
possibly-unresolved-reference = "warn"
|
||||||
|
possibly-missing-import = "warn"
|
||||||
unused-ignore-comment = "warn"
|
unused-ignore-comment = "warn"
|
||||||
division-by-zero = "warn"
|
division-by-zero = "warn"
|
||||||
|
|||||||
15
.github/renovate.json5
vendored
15
.github/renovate.json5
vendored
@@ -2,12 +2,11 @@
|
|||||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
dependencyDashboard: true,
|
dependencyDashboard: true,
|
||||||
suppressNotifications: ["prEditedNotification"],
|
suppressNotifications: ["prEditedNotification"],
|
||||||
extends: ["config:recommended"],
|
extends: ["github>astral-sh/renovate-config"],
|
||||||
labels: ["internal"],
|
labels: ["internal"],
|
||||||
schedule: ["before 4am on Monday"],
|
schedule: ["before 4am on Monday"],
|
||||||
semanticCommits: "disabled",
|
semanticCommits: "disabled",
|
||||||
separateMajorMinor: false,
|
separateMajorMinor: false,
|
||||||
prHourlyLimit: 10,
|
|
||||||
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "pip_requirements", "npm"],
|
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "pip_requirements", "npm"],
|
||||||
cargo: {
|
cargo: {
|
||||||
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
|
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
|
||||||
@@ -16,7 +15,7 @@
|
|||||||
pep621: {
|
pep621: {
|
||||||
// The default for this package manager is to only search for `pyproject.toml` files
|
// The default for this package manager is to only search for `pyproject.toml` files
|
||||||
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
|
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
|
||||||
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
|
managerFilePatterns: ["^(python|scripts)/.*pyproject\\.toml$"],
|
||||||
},
|
},
|
||||||
pip_requirements: {
|
pip_requirements: {
|
||||||
// The default for this package manager is to run on all requirements.txt files:
|
// The default for this package manager is to run on all requirements.txt files:
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
npm: {
|
npm: {
|
||||||
// The default for this package manager is to only search for `package.json` files
|
// The default for this package manager is to only search for `package.json` files
|
||||||
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
|
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
|
||||||
fileMatch: ["^playground/.*package\\.json$"],
|
managerFilePatterns: ["^playground/.*package\\.json$"],
|
||||||
},
|
},
|
||||||
"pre-commit": {
|
"pre-commit": {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -76,14 +75,6 @@
|
|||||||
matchManagers: ["cargo"],
|
matchManagers: ["cargo"],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// `mkdocs-material` requires a manual update to keep the version in sync
|
|
||||||
// with `mkdocs-material-insider`.
|
|
||||||
// See: https://squidfunk.github.io/mkdocs-material/insiders/upgrade/
|
|
||||||
matchManagers: ["pip_requirements"],
|
|
||||||
matchPackageNames: ["mkdocs-material"],
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
groupName: "pre-commit dependencies",
|
groupName: "pre-commit dependencies",
|
||||||
matchManagers: ["pre-commit"],
|
matchManagers: ["pre-commit"],
|
||||||
|
|||||||
16
.github/workflows/build-binaries.yml
vendored
16
.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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -300,7 +300,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
@@ -365,7 +365,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -431,7 +431,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
|
|||||||
113
.github/workflows/ci.yaml
vendored
113
.github/workflows/ci.yaml
vendored
@@ -24,6 +24,8 @@ env:
|
|||||||
PACKAGE_NAME: ruff
|
PACKAGE_NAME: ruff
|
||||||
PYTHON_VERSION: "3.14"
|
PYTHON_VERSION: "3.14"
|
||||||
NEXTEST_PROFILE: ci
|
NEXTEST_PROFILE: ci
|
||||||
|
# Enable mdtests that require external dependencies
|
||||||
|
MDTEST_EXTERNAL: "1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
determine_changes:
|
determine_changes:
|
||||||
@@ -230,7 +232,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -252,7 +254,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
shared-key: ruff-linux-debug
|
shared-key: ruff-linux-debug
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
@@ -261,11 +263,11 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
@@ -284,6 +286,10 @@ jobs:
|
|||||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||||
- name: Dogfood ty on py-fuzzer
|
- name: Dogfood ty on py-fuzzer
|
||||||
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
|
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
|
||||||
|
- name: Dogfood ty on the scripts directory
|
||||||
|
run: uv run --project=./scripts cargo run -p ty check --project=./scripts
|
||||||
|
- name: Dogfood ty on ty_benchmark
|
||||||
|
run: uv run --project=./scripts/ty_benchmark cargo run -p ty check --project=./scripts/ty_benchmark
|
||||||
# Check for broken links in the documentation.
|
# Check for broken links in the documentation.
|
||||||
- run: cargo doc --all --no-deps
|
- run: cargo doc --all --no-deps
|
||||||
env:
|
env:
|
||||||
@@ -292,7 +298,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 ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
|
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db -p ruff_python_formatter --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"
|
||||||
@@ -311,7 +317,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -319,7 +325,7 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
@@ -346,13 +352,13 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- 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@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
@@ -374,7 +380,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -411,7 +417,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
file: "Cargo.toml"
|
file: "Cargo.toml"
|
||||||
field: "workspace.package.rust-version"
|
field: "workspace.package.rust-version"
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -435,7 +441,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
workspaces: "fuzz -> target"
|
workspaces: "fuzz -> target"
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
@@ -444,7 +450,7 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo-binstall"
|
- name: "Install cargo-binstall"
|
||||||
uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
|
uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
|
||||||
- name: "Install cargo-fuzz"
|
- name: "Install cargo-fuzz"
|
||||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||||
@@ -463,7 +469,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
shared-key: ruff-linux-debug
|
shared-key: ruff-linux-debug
|
||||||
save-if: false
|
save-if: false
|
||||||
@@ -494,7 +500,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
@@ -543,7 +549,7 @@ jobs:
|
|||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
shared-key: ruff-linux-debug
|
shared-key: ruff-linux-debug
|
||||||
save-if: false
|
save-if: false
|
||||||
@@ -639,7 +645,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -684,7 +690,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
|
- uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
|
||||||
- run: cargo binstall --no-confirm cargo-shear
|
- run: cargo binstall --no-confirm cargo-shear
|
||||||
- run: cargo shear
|
- run: cargo shear
|
||||||
|
|
||||||
@@ -698,7 +704,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -719,11 +725,11 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
@@ -749,7 +755,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
@@ -775,20 +781,13 @@ jobs:
|
|||||||
name: "mkdocs"
|
name: "mkdocs"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
|
||||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Add SSH key"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
|
||||||
with:
|
|
||||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
@@ -796,11 +795,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.13
|
python-version: 3.13
|
||||||
activate-environment: true
|
activate-environment: true
|
||||||
- name: "Install Insiders dependencies"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
run: uv pip install -r docs/requirements-insiders.txt
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
|
||||||
run: uv pip install -r docs/requirements.txt
|
run: uv pip install -r docs/requirements.txt
|
||||||
- name: "Update README File"
|
- name: "Update README File"
|
||||||
run: python scripts/transform_readme.py --target mkdocs
|
run: python scripts/transform_readme.py --target mkdocs
|
||||||
@@ -808,12 +803,8 @@ jobs:
|
|||||||
run: python scripts/generate_mkdocs.py
|
run: python scripts/generate_mkdocs.py
|
||||||
- name: "Check docs formatting"
|
- name: "Check docs formatting"
|
||||||
run: python scripts/check_docs_formatted.py
|
run: python scripts/check_docs_formatted.py
|
||||||
- name: "Build Insiders docs"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
|
||||||
- name: "Build docs"
|
- name: "Build docs"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
run: mkdocs build --strict -f mkdocs.yml
|
||||||
run: mkdocs build --strict -f mkdocs.public.yml
|
|
||||||
|
|
||||||
check-formatter-instability-and-black-similarity:
|
check-formatter-instability-and-black-similarity:
|
||||||
name: "formatter instabilities and black similarity"
|
name: "formatter instabilities and black similarity"
|
||||||
@@ -825,7 +816,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -853,7 +844,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
shared-key: ruff-linux-debug
|
shared-key: ruff-linux-debug
|
||||||
save-if: false
|
save-if: false
|
||||||
@@ -871,7 +862,7 @@ jobs:
|
|||||||
repository: "astral-sh/ruff-lsp"
|
repository: "astral-sh/ruff-lsp"
|
||||||
path: ruff-lsp
|
path: ruff-lsp
|
||||||
|
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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"
|
||||||
@@ -904,7 +895,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: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
@@ -914,7 +905,7 @@ jobs:
|
|||||||
cache-dependency-path: playground/package-lock.json
|
cache-dependency-path: playground/package-lock.json
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
- name: "Install Node dependencies"
|
- name: "Install Node dependencies"
|
||||||
run: npm ci
|
run: npm ci --ignore-scripts
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Build playgrounds"
|
- name: "Build playgrounds"
|
||||||
run: npm run dev:wasm
|
run: npm run dev:wasm
|
||||||
@@ -938,13 +929,16 @@ jobs:
|
|||||||
needs.determine_changes.outputs.linter == 'true'
|
needs.determine_changes.outputs.linter == 'true'
|
||||||
)
|
)
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
permissions:
|
||||||
|
contents: read # required for actions/checkout
|
||||||
|
id-token: write # required for OIDC authentication with CodSpeed
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout Branch"
|
- name: "Checkout Branch"
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
@@ -953,7 +947,7 @@ jobs:
|
|||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
@@ -961,11 +955,10 @@ jobs:
|
|||||||
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
|
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||||
with:
|
with:
|
||||||
mode: instrumentation
|
mode: simulation
|
||||||
run: cargo codspeed run
|
run: cargo codspeed run
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
||||||
|
|
||||||
benchmarks-instrumented-ty:
|
benchmarks-instrumented-ty:
|
||||||
name: "benchmarks instrumented (ty)"
|
name: "benchmarks instrumented (ty)"
|
||||||
@@ -978,13 +971,16 @@ jobs:
|
|||||||
needs.determine_changes.outputs.ty == 'true'
|
needs.determine_changes.outputs.ty == 'true'
|
||||||
)
|
)
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
permissions:
|
||||||
|
contents: read # required for actions/checkout
|
||||||
|
id-token: write # required for OIDC authentication with CodSpeed
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout Branch"
|
- name: "Checkout Branch"
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
@@ -993,7 +989,7 @@ jobs:
|
|||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
@@ -1001,11 +997,10 @@ jobs:
|
|||||||
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
|
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||||
with:
|
with:
|
||||||
mode: instrumentation
|
mode: simulation
|
||||||
run: cargo codspeed run
|
run: cargo codspeed run
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
||||||
|
|
||||||
benchmarks-walltime:
|
benchmarks-walltime:
|
||||||
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
||||||
@@ -1013,6 +1008,9 @@ jobs:
|
|||||||
needs: determine_changes
|
needs: determine_changes
|
||||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
permissions:
|
||||||
|
contents: read # required for actions/checkout
|
||||||
|
id-token: write # required for OIDC authentication with CodSpeed
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
benchmarks:
|
benchmarks:
|
||||||
@@ -1024,7 +1022,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
@@ -1033,7 +1031,7 @@ jobs:
|
|||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
@@ -1041,7 +1039,7 @@ jobs:
|
|||||||
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
|
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
|
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||||
env:
|
env:
|
||||||
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
||||||
# appear to provide much useful insight for our walltime benchmarks right now
|
# appear to provide much useful insight for our walltime benchmarks right now
|
||||||
@@ -1050,4 +1048,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mode: walltime
|
mode: walltime
|
||||||
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
||||||
|
|||||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
- name: Build ruff
|
- name: Build ruff
|
||||||
# A debug build means the script runs slower once it gets started,
|
# A debug build means the script runs slower once it gets started,
|
||||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||||
|
|||||||
57
.github/workflows/mypy_primer.yaml
vendored
57
.github/workflows/mypy_primer.yaml
vendored
@@ -45,8 +45,9 @@ jobs:
|
|||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
|
shared-key: "mypy-primer"
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
@@ -83,9 +84,10 @@ jobs:
|
|||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
shared-key: "mypy-primer"
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: rustup show
|
run: rustup show
|
||||||
@@ -105,3 +107,54 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: mypy_primer_memory_diff
|
name: mypy_primer_memory_diff
|
||||||
path: mypy_primer_memory.diff
|
path: mypy_primer_memory.diff
|
||||||
|
|
||||||
|
# Runs mypy twice against the same ty version to catch any non-deterministic behavior (ideally).
|
||||||
|
# The job is disabled for now because there are some non-deterministic diagnostics.
|
||||||
|
mypy_primer_same_revision:
|
||||||
|
name: Run mypy_primer on same revision
|
||||||
|
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||||
|
timeout-minutes: 20
|
||||||
|
# TODO: Enable once we fixed the non-deterministic diagnostics
|
||||||
|
if: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
path: ruff
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
|
with:
|
||||||
|
workspaces: "ruff"
|
||||||
|
shared-key: "mypy-primer"
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: rustup show
|
||||||
|
|
||||||
|
- name: Run determinism check
|
||||||
|
env:
|
||||||
|
BASE_REVISION: ${{ github.event.pull_request.head.sha }}
|
||||||
|
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||||
|
CLICOLOR_FORCE: "1"
|
||||||
|
DIFF_FILE: mypy_primer_determinism.diff
|
||||||
|
run: |
|
||||||
|
cd ruff
|
||||||
|
scripts/mypy_primer.sh
|
||||||
|
|
||||||
|
- name: Check for non-determinism
|
||||||
|
run: |
|
||||||
|
# Remove ANSI color codes for checking
|
||||||
|
sed -e 's/\x1b\[[0-9;]*m//g' mypy_primer_determinism.diff > mypy_primer_determinism_clean.diff
|
||||||
|
|
||||||
|
# Check if there are any differences (non-determinism)
|
||||||
|
if [ -s mypy_primer_determinism_clean.diff ]; then
|
||||||
|
echo "ERROR: Non-deterministic output detected!"
|
||||||
|
echo "The following differences were found when running ty twice on the same commit:"
|
||||||
|
cat mypy_primer_determinism_clean.diff
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "✓ Output is deterministic"
|
||||||
|
fi
|
||||||
|
|||||||
24
.github/workflows/publish-docs.yml
vendored
24
.github/workflows/publish-docs.yml
vendored
@@ -20,15 +20,13 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
mkdocs:
|
mkdocs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
|
|
||||||
@@ -59,23 +57,12 @@ jobs:
|
|||||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: "Add SSH key"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
|
||||||
with:
|
|
||||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
|
|
||||||
- name: "Install Insiders dependencies"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
run: pip install -r docs/requirements-insiders.txt
|
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
|
||||||
run: pip install -r docs/requirements.txt
|
run: pip install -r docs/requirements.txt
|
||||||
|
|
||||||
- name: "Copy README File"
|
- name: "Copy README File"
|
||||||
@@ -83,13 +70,8 @@ jobs:
|
|||||||
python scripts/transform_readme.py --target mkdocs
|
python scripts/transform_readme.py --target mkdocs
|
||||||
python scripts/generate_mkdocs.py
|
python scripts/generate_mkdocs.py
|
||||||
|
|
||||||
- name: "Build Insiders docs"
|
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
|
||||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
|
||||||
|
|
||||||
- name: "Build docs"
|
- name: "Build docs"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
run: mkdocs build --strict -f mkdocs.yml
|
||||||
run: mkdocs build --strict -f mkdocs.public.yml
|
|
||||||
|
|
||||||
- name: "Clone docs repo"
|
- name: "Clone docs repo"
|
||||||
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||||
|
|||||||
2
.github/workflows/publish-playground.yml
vendored
2
.github/workflows/publish-playground.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
- name: "Install Node dependencies"
|
- name: "Install Node dependencies"
|
||||||
run: npm ci
|
run: npm ci --ignore-scripts
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Run TypeScript checks"
|
- name: "Run TypeScript checks"
|
||||||
run: npm run check
|
run: npm run check
|
||||||
|
|||||||
2
.github/workflows/publish-ty-playground.yml
vendored
2
.github/workflows/publish-ty-playground.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
- name: "Install Node dependencies"
|
- name: "Install Node dependencies"
|
||||||
run: npm ci
|
run: npm ci --ignore-scripts
|
||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Run TypeScript checks"
|
- name: "Run TypeScript checks"
|
||||||
run: npm run check
|
run: npm run check
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -123,7 +123,7 @@ 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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -174,7 +174,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
val: ${{ steps.host.outputs.manifest }}
|
val: ${{ steps.host.outputs.manifest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -250,7 +250,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|||||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -198,7 +198,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||||
git commit -am "Remove pyproject.toml file"
|
git commit -am "Remove pyproject.toml file"
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
run: rustup show
|
run: rustup show
|
||||||
@@ -207,12 +207,12 @@ jobs:
|
|||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: Update snapshots
|
- name: Update snapshots
|
||||||
|
|||||||
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||||
|
|
||||||
ecosystem-analyzer \
|
ecosystem-analyzer \
|
||||||
--repository ruff \
|
--repository ruff \
|
||||||
|
|||||||
4
.github/workflows/ty-ecosystem-report.yaml
vendored
4
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||||
|
|
||||||
ecosystem-analyzer \
|
ecosystem-analyzer \
|
||||||
--verbose \
|
--verbose \
|
||||||
|
|||||||
2
.github/workflows/typing_conformance.yaml
vendored
2
.github/workflows/typing_conformance.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
path: typing
|
path: typing
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -5,5 +5,6 @@
|
|||||||
"rust-analyzer.check.command": "clippy",
|
"rust-analyzer.check.command": "clippy",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/*.snap": true
|
"**/*.snap": true
|
||||||
}
|
},
|
||||||
|
"ty.diagnosticMode": "openFilesOnly"
|
||||||
}
|
}
|
||||||
|
|||||||
155
CHANGELOG.md
155
CHANGELOG.md
@@ -1,5 +1,160 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.14.10
|
||||||
|
|
||||||
|
Released on 2025-12-18.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- [formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
|
||||||
|
- [formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
|
||||||
|
- \[`flake8-implicit-str-concat`\] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
|
||||||
|
- \[`flake8-use-pathlib`\] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
|
||||||
|
- \[`refurb`\] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- \[`pyupgrade`\] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`eradicate`\] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
|
||||||
|
- \[`flake8-pytest-style`\] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
|
||||||
|
- [syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
|
||||||
|
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
|
||||||
|
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
|
||||||
|
- \[`flake8-bandit`\] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@dylwil3](https://github.com/dylwil3)
|
||||||
|
- [@charliecloudberry](https://github.com/charliecloudberry)
|
||||||
|
- [@charliermarsh](https://github.com/charliermarsh)
|
||||||
|
- [@chirizxc](https://github.com/chirizxc)
|
||||||
|
- [@ntBre](https://github.com/ntBre)
|
||||||
|
- [@zanieb](https://github.com/zanieb)
|
||||||
|
- [@amyreese](https://github.com/amyreese)
|
||||||
|
- [@hauntsaninja](https://github.com/hauntsaninja)
|
||||||
|
- [@11happy](https://github.com/11happy)
|
||||||
|
- [@mahiro72](https://github.com/mahiro72)
|
||||||
|
- [@MichaReiser](https://github.com/MichaReiser)
|
||||||
|
- [@phongddo](https://github.com/phongddo)
|
||||||
|
- [@PeterJCLaw](https://github.com/PeterJCLaw)
|
||||||
|
|
||||||
|
## 0.14.9
|
||||||
|
|
||||||
|
Released on 2025-12-11.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`ruff`\] New `RUF100` diagnostics for unused range suppressions ([#21783](https://github.com/astral-sh/ruff/pull/21783))
|
||||||
|
- \[`pylint`\] Detect subclasses of builtin exceptions (`PLW0133`) ([#21382](https://github.com/astral-sh/ruff/pull/21382))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix comment placement in lambda parameters ([#21868](https://github.com/astral-sh/ruff/pull/21868))
|
||||||
|
- Skip over trivia tokens after re-lexing ([#21895](https://github.com/astral-sh/ruff/pull/21895))
|
||||||
|
- \[`flake8-bandit`\] Fix false positive when using non-standard `CSafeLoader` path (S506). ([#21830](https://github.com/astral-sh/ruff/pull/21830))
|
||||||
|
- \[`flake8-bugbear`\] Accept immutable slice default arguments (`B008`) ([#21823](https://github.com/astral-sh/ruff/pull/21823))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`pydocstyle`\] Suppress `D417` for parameters with `Unpack` annotations ([#21816](https://github.com/astral-sh/ruff/pull/21816))
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Use `memchr` for computing line indexes ([#21838](https://github.com/astral-sh/ruff/pull/21838))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Document `*.pyw` is included by default in preview ([#21885](https://github.com/astral-sh/ruff/pull/21885))
|
||||||
|
- Document range suppressions, reorganize suppression docs ([#21884](https://github.com/astral-sh/ruff/pull/21884))
|
||||||
|
- Update mkdocs-material to 9.7.0 (Insiders now free) ([#21797](https://github.com/astral-sh/ruff/pull/21797))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@Avasam](https://github.com/Avasam)
|
||||||
|
- [@MichaReiser](https://github.com/MichaReiser)
|
||||||
|
- [@charliermarsh](https://github.com/charliermarsh)
|
||||||
|
- [@amyreese](https://github.com/amyreese)
|
||||||
|
- [@phongddo](https://github.com/phongddo)
|
||||||
|
- [@prakhar1144](https://github.com/prakhar1144)
|
||||||
|
- [@mahiro72](https://github.com/mahiro72)
|
||||||
|
- [@ntBre](https://github.com/ntBre)
|
||||||
|
- [@LoicRiegel](https://github.com/LoicRiegel)
|
||||||
|
|
||||||
|
## 0.14.8
|
||||||
|
|
||||||
|
Released on 2025-12-04.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`flake8-bugbear`\] Catch `yield` expressions within other statements (`B901`) ([#21200](https://github.com/astral-sh/ruff/pull/21200))
|
||||||
|
- \[`flake8-use-pathlib`\] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#21440](https://github.com/astral-sh/ruff/pull/21440))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix syntax error false positives for `await` outside functions ([#21763](https://github.com/astral-sh/ruff/pull/21763))
|
||||||
|
- \[`flake8-simplify`\] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) ([#21479](https://github.com/astral-sh/ruff/pull/21479))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Suggest using `--output-file` option in GitLab integration ([#21706](https://github.com/astral-sh/ruff/pull/21706))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- [syntax-error] Default type parameter followed by non-default type parameter ([#21657](https://github.com/astral-sh/ruff/pull/21657))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@kieran-ryan](https://github.com/kieran-ryan)
|
||||||
|
- [@11happy](https://github.com/11happy)
|
||||||
|
- [@danparizher](https://github.com/danparizher)
|
||||||
|
- [@ntBre](https://github.com/ntBre)
|
||||||
|
|
||||||
|
## 0.14.7
|
||||||
|
|
||||||
|
Released on 2025-11-28.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`flake8-bandit`\] Handle string literal bindings in suspicious-url-open-usage (`S310`) ([#21469](https://github.com/astral-sh/ruff/pull/21469))
|
||||||
|
- \[`pylint`\] Fix `PLR1708` false positives on nested functions ([#21177](https://github.com/astral-sh/ruff/pull/21177))
|
||||||
|
- \[`pylint`\] Fix suppression for empty dict without tuple key annotation (`PLE1141`) ([#21290](https://github.com/astral-sh/ruff/pull/21290))
|
||||||
|
- \[`ruff`\] Add rule `RUF066` to detect unnecessary class properties ([#21535](https://github.com/astral-sh/ruff/pull/21535))
|
||||||
|
- \[`ruff`\] Catch more dummy variable uses (`RUF052`) ([#19799](https://github.com/astral-sh/ruff/pull/19799))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- [server] Set severity for non-rule diagnostics ([#21559](https://github.com/astral-sh/ruff/pull/21559))
|
||||||
|
- \[`flake8-implicit-str-concat`\] Avoid invalid fix in (`ISC003`) ([#21517](https://github.com/astral-sh/ruff/pull/21517))
|
||||||
|
- \[`parser`\] Fix panic when parsing IPython escape command expressions ([#21480](https://github.com/astral-sh/ruff/pull/21480))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Show partial fixability indicator in statistics output ([#21513](https://github.com/astral-sh/ruff/pull/21513))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@mikeleppane](https://github.com/mikeleppane)
|
||||||
|
- [@senekor](https://github.com/senekor)
|
||||||
|
- [@ShaharNaveh](https://github.com/ShaharNaveh)
|
||||||
|
- [@JumboBear](https://github.com/JumboBear)
|
||||||
|
- [@prakhar1144](https://github.com/prakhar1144)
|
||||||
|
- [@tsvikas](https://github.com/tsvikas)
|
||||||
|
- [@danparizher](https://github.com/danparizher)
|
||||||
|
- [@chirizxc](https://github.com/chirizxc)
|
||||||
|
- [@AlexWaygood](https://github.com/AlexWaygood)
|
||||||
|
- [@MichaReiser](https://github.com/MichaReiser)
|
||||||
|
|
||||||
## 0.14.6
|
## 0.14.6
|
||||||
|
|
||||||
Released on 2025-11-21.
|
Released on 2025-11-21.
|
||||||
|
|||||||
@@ -331,13 +331,6 @@ you addressed them.
|
|||||||
|
|
||||||
## MkDocs
|
## MkDocs
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
>
|
|
||||||
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
|
|
||||||
> This means only members of the Astral organization can preview the documentation exactly as it
|
|
||||||
> will appear in production.
|
|
||||||
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
|
|
||||||
|
|
||||||
To preview any changes to the documentation locally:
|
To preview any changes to the documentation locally:
|
||||||
|
|
||||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||||
@@ -351,11 +344,7 @@ To preview any changes to the documentation locally:
|
|||||||
1. Run the development server with:
|
1. Run the development server with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# For contributors.
|
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
|
||||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
|
|
||||||
|
|
||||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
|
||||||
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The documentation should then be available locally at
|
The documentation should then be available locally at
|
||||||
|
|||||||
141
Cargo.lock
generated
141
Cargo.lock
generated
@@ -254,6 +254,21 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -944,6 +959,18 @@ dependencies = [
|
|||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "datatest-stable"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82"
|
||||||
|
dependencies = [
|
||||||
|
"camino",
|
||||||
|
"fancy-regex",
|
||||||
|
"libtest-mimic 0.8.1",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-where"
|
name = "derive-where"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -977,27 +1004,6 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dir-test"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62c013fe825864f3e4593f36426c1fa7a74f5603f13ca8d1af7a990c1cd94a79"
|
|
||||||
dependencies = [
|
|
||||||
"dir-test-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dir-test-macros"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d42f54d7b4a6bc2400fe5b338e35d1a335787585375322f49c5d5fe7b243da7e"
|
|
||||||
dependencies = [
|
|
||||||
"glob",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -1016,7 +1022,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1108,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1138,6 +1144,17 @@ dependencies = [
|
|||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fancy-regex"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -1238,9 +1255,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "get-size-derive2"
|
name = "get-size-derive2"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd"
|
checksum = "ab21d7bd2c625f2064f04ce54bcb88bc57c45724cde45cba326d784e22d3f71a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"attribute-derive",
|
"attribute-derive",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1249,14 +1266,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "get-size2"
|
name = "get-size2"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
|
checksum = "879272b0de109e2b67b39fcfe3d25fdbba96ac07e44a254f5a0b4d7ff55340cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"get-size-derive2",
|
"get-size-derive2",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"ordermap",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1624,7 +1642,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
|
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console 0.15.11",
|
"console 0.15.11",
|
||||||
"globset",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
@@ -1632,7 +1649,6 @@ dependencies = [
|
|||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"similar",
|
"similar",
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1763,7 +1779,7 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"portable-atomic-util",
|
"portable-atomic-util",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1918,6 +1934,18 @@ dependencies = [
|
|||||||
"threadpool",
|
"threadpool",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libtest-mimic"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap",
|
||||||
|
"escape8259",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -2233,9 +2261,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordermap"
|
name = "ordermap"
|
||||||
version = "0.5.12"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
|
checksum = "ed637741ced8fb240855d22a2b4f208dab7a06bcce73380162e5253000c16758"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2859,7 +2887,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.6"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argfile",
|
"argfile",
|
||||||
@@ -3117,13 +3145,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.14.6"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"clap",
|
"clap",
|
||||||
"colored 3.0.0",
|
"colored 3.0.0",
|
||||||
|
"compact_str",
|
||||||
"fern",
|
"fern",
|
||||||
"glob",
|
"glob",
|
||||||
"globset",
|
"globset",
|
||||||
@@ -3276,6 +3305,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"countme",
|
"countme",
|
||||||
|
"datatest-stable",
|
||||||
"insta",
|
"insta",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -3345,8 +3375,10 @@ dependencies = [
|
|||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bstr",
|
"bstr",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
|
"datatest-stable",
|
||||||
"get-size2",
|
"get-size2",
|
||||||
"insta",
|
"insta",
|
||||||
|
"itertools 0.14.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ruff_annotate_snippets",
|
"ruff_annotate_snippets",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
@@ -3472,7 +3504,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_wasm"
|
name = "ruff_wasm"
|
||||||
version = "0.14.6"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
"console_log",
|
||||||
@@ -3570,7 +3602,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3588,7 +3620,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "salsa"
|
name = "salsa"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"boxcar",
|
"boxcar",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
@@ -3599,6 +3631,7 @@ dependencies = [
|
|||||||
"indexmap",
|
"indexmap",
|
||||||
"intrusive-collections",
|
"intrusive-collections",
|
||||||
"inventory",
|
"inventory",
|
||||||
|
"ordermap",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
@@ -3612,12 +3645,12 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "salsa-macro-rules"
|
name = "salsa-macro-rules"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "salsa-macros"
|
name = "salsa-macros"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3971,7 +4004,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4216,9 +4249,9 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -4228,9 +4261,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.30"
|
version = "0.1.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -4239,9 +4272,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.34"
|
version = "0.1.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
"valuable",
|
||||||
@@ -4283,9 +4316,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.20"
|
version = "0.3.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"matchers",
|
"matchers",
|
||||||
@@ -4307,7 +4340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
|
checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ignore",
|
"ignore",
|
||||||
"libtest-mimic",
|
"libtest-mimic 0.7.3",
|
||||||
"snapbox",
|
"snapbox",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4336,6 +4369,7 @@ dependencies = [
|
|||||||
"ruff_python_trivia",
|
"ruff_python_trivia",
|
||||||
"salsa",
|
"salsa",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"tikv-jemallocator",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-flame",
|
"tracing-flame",
|
||||||
@@ -4458,7 +4492,7 @@ dependencies = [
|
|||||||
"camino",
|
"camino",
|
||||||
"colored 3.0.0",
|
"colored 3.0.0",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"dir-test",
|
"datatest-stable",
|
||||||
"drop_bomb",
|
"drop_bomb",
|
||||||
"get-size2",
|
"get-size2",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -4474,6 +4508,7 @@ dependencies = [
|
|||||||
"quickcheck_macros",
|
"quickcheck_macros",
|
||||||
"ruff_annotate_snippets",
|
"ruff_annotate_snippets",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
|
"ruff_diagnostics",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
"ruff_macros",
|
"ruff_macros",
|
||||||
"ruff_memory_usage",
|
"ruff_memory_usage",
|
||||||
@@ -4519,6 +4554,7 @@ dependencies = [
|
|||||||
"lsp-types",
|
"lsp-types",
|
||||||
"regex",
|
"regex",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
|
"ruff_diagnostics",
|
||||||
"ruff_macros",
|
"ruff_macros",
|
||||||
"ruff_notebook",
|
"ruff_notebook",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
@@ -4554,11 +4590,13 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"camino",
|
"camino",
|
||||||
"colored 3.0.0",
|
"colored 3.0.0",
|
||||||
|
"dunce",
|
||||||
"insta",
|
"insta",
|
||||||
"memchr",
|
"memchr",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
"regex",
|
"regex",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
|
"ruff_diagnostics",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
"ruff_notebook",
|
"ruff_notebook",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
@@ -4600,6 +4638,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
|
"ruff_diagnostics",
|
||||||
"ruff_notebook",
|
"ruff_notebook",
|
||||||
"ruff_python_formatter",
|
"ruff_python_formatter",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
@@ -5020,7 +5059,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
# Please update rustfmt.toml when bumping the Rust edition
|
# Please update rustfmt.toml when bumping the Rust edition
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.89"
|
rust-version = "1.90"
|
||||||
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"
|
||||||
@@ -81,14 +81,14 @@ compact_str = "0.9.0"
|
|||||||
criterion = { version = "0.7.0", default-features = false }
|
criterion = { version = "0.7.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" }
|
datatest-stable = { version = "0.3.3" }
|
||||||
dunce = { version = "1.0.5" }
|
dunce = { version = "1.0.5" }
|
||||||
drop_bomb = { version = "0.1.5" }
|
drop_bomb = { version = "0.1.5" }
|
||||||
etcetera = { version = "0.11.0" }
|
etcetera = { version = "0.11.0" }
|
||||||
fern = { version = "0.7.0" }
|
fern = { version = "0.7.0" }
|
||||||
filetime = { version = "0.2.23" }
|
filetime = { version = "0.2.23" }
|
||||||
getrandom = { version = "0.3.1" }
|
getrandom = { version = "0.3.1" }
|
||||||
get-size2 = { version = "0.7.0", features = [
|
get-size2 = { version = "0.7.3", features = [
|
||||||
"derive",
|
"derive",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -129,7 +129,7 @@ memchr = { version = "2.7.1" }
|
|||||||
mimalloc = { version = "0.1.39" }
|
mimalloc = { version = "0.1.39" }
|
||||||
natord = { version = "1.0.9" }
|
natord = { version = "1.0.9" }
|
||||||
notify = { version = "8.0.0" }
|
notify = { version = "8.0.0" }
|
||||||
ordermap = { version = "0.5.0" }
|
ordermap = { version = "1.0.0" }
|
||||||
path-absolutize = { version = "3.1.1" }
|
path-absolutize = { version = "3.1.1" }
|
||||||
path-slash = { version = "0.2.1" }
|
path-slash = { version = "0.2.1" }
|
||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
@@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" }
|
|||||||
rustc-hash = { version = "2.0.0" }
|
rustc-hash = { version = "2.0.0" }
|
||||||
rustc-stable-hash = { version = "0.1.2" }
|
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 = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [
|
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"macros",
|
"macros",
|
||||||
"salsa_unstable",
|
"salsa_unstable",
|
||||||
@@ -272,6 +272,12 @@ large_stack_arrays = "allow"
|
|||||||
lto = "fat"
|
lto = "fat"
|
||||||
codegen-units = 16
|
codegen-units = 16
|
||||||
|
|
||||||
|
# Profile to build a minimally sized binary for ruff/ty
|
||||||
|
[profile.minimal-size]
|
||||||
|
inherits = "release"
|
||||||
|
opt-level = "z"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
# Some crates don't change as much but benefit more from
|
# Some crates don't change as much but benefit more from
|
||||||
# more expensive optimization passes, so we selectively
|
# more expensive optimization passes, so we selectively
|
||||||
# decrease codegen-units in some cases.
|
# decrease codegen-units in some cases.
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -57,8 +57,11 @@ Ruff is extremely actively developed and used in major open-source projects like
|
|||||||
|
|
||||||
...and [many more](#whos-using-ruff).
|
...and [many more](#whos-using-ruff).
|
||||||
|
|
||||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
Ruff is backed by [Astral](https://astral.sh), the creators of
|
||||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
||||||
|
|
||||||
|
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
||||||
|
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||||
|
|
||||||
## Testimonials
|
## Testimonials
|
||||||
|
|
||||||
@@ -147,8 +150,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.14.6/install.sh | sh
|
curl -LsSf https://astral.sh/ruff/0.14.10/install.sh | sh
|
||||||
powershell -c "irm https://astral.sh/ruff/0.14.6/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/0.14.10/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),
|
||||||
@@ -181,7 +184,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.14.6
|
rev: v0.14.10
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ extend-exclude = [
|
|||||||
"crates/ty_vendored/vendor/**/*",
|
"crates/ty_vendored/vendor/**/*",
|
||||||
"**/resources/**/*",
|
"**/resources/**/*",
|
||||||
"**/snapshots/**/*",
|
"**/snapshots/**/*",
|
||||||
|
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
|
||||||
# Completion tests tend to have a lot of incomplete
|
# Completion tests tend to have a lot of incomplete
|
||||||
# words naturally. It's annoying to have to make all
|
# words naturally. It's annoying to have to make all
|
||||||
# of them actually words. So just ignore typos here.
|
# of them actually words. So just ignore typos here.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.6"
|
version = "0.14.10"
|
||||||
publish = true
|
publish = true
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use anyhow::bail;
|
|||||||
use clap::builder::Styles;
|
use clap::builder::Styles;
|
||||||
use clap::builder::styling::{AnsiColor, Effects};
|
use clap::builder::styling::{AnsiColor, Effects};
|
||||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||||
use clap::{Parser, Subcommand, command};
|
use clap::{Parser, Subcommand};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::{error, warn};
|
use log::error;
|
||||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||||
|
|
||||||
use args::{GlobalConfigArgs, ServerCommand};
|
use args::{GlobalConfigArgs, ServerCommand};
|
||||||
|
|||||||
@@ -34,9 +34,21 @@ struct ExpandedStatistics<'a> {
|
|||||||
code: Option<&'a SecondaryCode>,
|
code: Option<&'a SecondaryCode>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
count: usize,
|
count: usize,
|
||||||
fixable: bool,
|
#[serde(rename = "fixable")]
|
||||||
|
all_fixable: bool,
|
||||||
|
fixable_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExpandedStatistics<'_> {
|
||||||
|
fn any_fixable(&self) -> bool {
|
||||||
|
self.fixable_count > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accumulator type for grouping diagnostics by code.
|
||||||
|
/// Format: (`code`, `representative_diagnostic`, `total_count`, `fixable_count`)
|
||||||
|
type DiagnosticGroup<'a> = (Option<&'a SecondaryCode>, &'a Diagnostic, usize, usize);
|
||||||
|
|
||||||
pub(crate) struct Printer {
|
pub(crate) struct Printer {
|
||||||
format: OutputFormat,
|
format: OutputFormat,
|
||||||
log_level: LogLevel,
|
log_level: LogLevel,
|
||||||
@@ -133,7 +145,7 @@ impl Printer {
|
|||||||
if fixables.applicable > 0 {
|
if fixables.applicable > 0 {
|
||||||
writeln!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"{fix_prefix} {} fixable with the --fix option.",
|
"{fix_prefix} {} fixable with the `--fix` option.",
|
||||||
fixables.applicable
|
fixables.applicable
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -256,35 +268,41 @@ impl Printer {
|
|||||||
diagnostics: &Diagnostics,
|
diagnostics: &Diagnostics,
|
||||||
writer: &mut dyn Write,
|
writer: &mut dyn Write,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let required_applicability = self.unsafe_fixes.required_applicability();
|
||||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||||
.inner
|
.inner
|
||||||
.iter()
|
.iter()
|
||||||
.map(|message| (message.secondary_code(), message))
|
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
|
||||||
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
.fold(vec![], |mut acc: Vec<DiagnosticGroup>, diagnostic| {
|
||||||
.fold(
|
let is_fixable = diagnostic
|
||||||
vec![],
|
.fix()
|
||||||
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
|
.is_some_and(|fix| fix.applies(required_applicability));
|
||||||
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
let code = diagnostic.secondary_code();
|
||||||
if *prev_code == code {
|
|
||||||
*count += 1;
|
if let Some((prev_code, _prev_message, count, fixable_count)) = acc.last_mut() {
|
||||||
return acc;
|
if *prev_code == code {
|
||||||
|
*count += 1;
|
||||||
|
if is_fixable {
|
||||||
|
*fixable_count += 1;
|
||||||
}
|
}
|
||||||
|
return acc;
|
||||||
}
|
}
|
||||||
acc.push(((code, message), 1));
|
}
|
||||||
acc
|
acc.push((code, diagnostic, 1, usize::from(is_fixable)));
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|&(code, message, count, fixable_count)| ExpandedStatistics {
|
||||||
|
code,
|
||||||
|
name: message.name(),
|
||||||
|
count,
|
||||||
|
// Backward compatibility: `fixable` is true only when all violations are fixable.
|
||||||
|
// See: https://github.com/astral-sh/ruff/pull/21513
|
||||||
|
all_fixable: fixable_count == count,
|
||||||
|
fixable_count,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.iter()
|
|
||||||
.map(|&((code, message), count)| ExpandedStatistics {
|
|
||||||
code,
|
|
||||||
name: message.name(),
|
|
||||||
count,
|
|
||||||
fixable: if let Some(fix) = message.fix() {
|
|
||||||
fix.applies(self.unsafe_fixes.required_applicability())
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.sorted_by_key(|statistic| Reverse(statistic.count))
|
.sorted_by_key(|statistic| Reverse(statistic.count))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -308,13 +326,14 @@ impl Printer {
|
|||||||
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
||||||
.max()
|
.max()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
let any_fixable = statistics.iter().any(ExpandedStatistics::any_fixable);
|
||||||
|
|
||||||
let fixable = format!("[{}] ", "*".cyan());
|
let all_fixable = format!("[{}] ", "*".cyan());
|
||||||
|
let partially_fixable = format!("[{}] ", "-".cyan());
|
||||||
let unfixable = "[ ] ";
|
let unfixable = "[ ] ";
|
||||||
|
|
||||||
// By default, we mimic Flake8's `--statistics` format.
|
// By default, we mimic Flake8's `--statistics` format.
|
||||||
for statistic in statistics {
|
for statistic in &statistics {
|
||||||
writeln!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"{:>count_width$}\t{:<code_width$}\t{}{}",
|
"{:>count_width$}\t{:<code_width$}\t{}{}",
|
||||||
@@ -326,8 +345,10 @@ impl Printer {
|
|||||||
.red()
|
.red()
|
||||||
.bold(),
|
.bold(),
|
||||||
if any_fixable {
|
if any_fixable {
|
||||||
if statistic.fixable {
|
if statistic.all_fixable {
|
||||||
&fixable
|
&all_fixable
|
||||||
|
} else if statistic.any_fixable() {
|
||||||
|
&partially_fixable
|
||||||
} else {
|
} else {
|
||||||
unfixable
|
unfixable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1440,6 +1440,78 @@ def function():
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignore_noqa() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
import os # noqa: F401
|
||||||
|
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import sys
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// without --ignore-noqa
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py"),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.args(["--preview"]),
|
||||||
|
@r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
// with --ignore-noqa --preview
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.args(["--ignore-noqa", "--preview"]),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
noqa.py:2:8: F401 [*] `os` imported but unused
|
||||||
|
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_noqa() -> Result<()> {
|
fn add_noqa() -> Result<()> {
|
||||||
let fixture = CliTest::new()?;
|
let fixture = CliTest::new()?;
|
||||||
@@ -1632,6 +1704,100 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_noqa_existing_file_level_noqa() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
# ruff: noqa F401
|
||||||
|
import os
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.arg("--preview")
|
||||||
|
.args(["--add-noqa"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
|
||||||
|
"#), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
let test_code =
|
||||||
|
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||||
|
|
||||||
|
insta::assert_snapshot!(test_code, @r"
|
||||||
|
# ruff: noqa F401
|
||||||
|
import os
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_noqa_existing_range_suppression() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import os
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.arg("--preview")
|
||||||
|
.args(["--add-noqa"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
|
||||||
|
"#), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
let test_code =
|
||||||
|
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||||
|
|
||||||
|
insta::assert_snapshot!(test_code, @r"
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import os
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_noqa_multiline_comment() -> Result<()> {
|
fn add_noqa_multiline_comment() -> Result<()> {
|
||||||
let fixture = CliTest::new()?;
|
let fixture = CliTest::new()?;
|
||||||
|
|||||||
@@ -1043,7 +1043,7 @@ def mvce(keys, values):
|
|||||||
----- stdout -----
|
----- stdout -----
|
||||||
1 C416 [*] unnecessary-comprehension
|
1 C416 [*] unnecessary-comprehension
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
");
|
");
|
||||||
@@ -1073,7 +1073,8 @@ def mvce(keys, values):
|
|||||||
"code": "C416",
|
"code": "C416",
|
||||||
"name": "unnecessary-comprehension",
|
"name": "unnecessary-comprehension",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"fixable": false
|
"fixable": false,
|
||||||
|
"fixable_count": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1106,7 +1107,8 @@ def mvce(keys, values):
|
|||||||
"code": "C416",
|
"code": "C416",
|
||||||
"name": "unnecessary-comprehension",
|
"name": "unnecessary-comprehension",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"fixable": true
|
"fixable": true,
|
||||||
|
"fixable_count": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1114,6 +1116,54 @@ def mvce(keys, values):
|
|||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_statistics_json_partial_fix() {
|
||||||
|
let mut cmd = RuffCheck::default()
|
||||||
|
.args([
|
||||||
|
"--select",
|
||||||
|
"UP035",
|
||||||
|
"--statistics",
|
||||||
|
"--output-format",
|
||||||
|
"json",
|
||||||
|
])
|
||||||
|
.build();
|
||||||
|
assert_cmd_snapshot!(cmd
|
||||||
|
.pass_stdin("from typing import List, AsyncGenerator"), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"code": "UP035",
|
||||||
|
"name": "deprecated-import",
|
||||||
|
"count": 2,
|
||||||
|
"fixable": false,
|
||||||
|
"fixable_count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_statistics_partial_fix() {
|
||||||
|
let mut cmd = RuffCheck::default()
|
||||||
|
.args(["--select", "UP035", "--statistics"])
|
||||||
|
.build();
|
||||||
|
assert_cmd_snapshot!(cmd
|
||||||
|
.pass_stdin("from typing import List, AsyncGenerator"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
2 UP035 [-] deprecated-import
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn show_statistics_syntax_errors() {
|
fn show_statistics_syntax_errors() {
|
||||||
let mut cmd = RuffCheck::default()
|
let mut cmd = RuffCheck::default()
|
||||||
@@ -1810,7 +1860,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
|
|||||||
--> -:1:1
|
--> -:1:1
|
||||||
|
|
||||||
Found 2 errors.
|
Found 2 errors.
|
||||||
[*] 1 fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
");
|
");
|
||||||
@@ -1853,7 +1903,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
|
|||||||
--> -:1:1
|
--> -:1:1
|
||||||
|
|
||||||
Found 2 errors.
|
Found 2 errors.
|
||||||
[*] 2 fixable with the --fix option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
");
|
");
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use criterion::{
|
|||||||
use ruff_benchmark::{
|
use ruff_benchmark::{
|
||||||
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||||
};
|
};
|
||||||
use ruff_python_parser::{Mode, TokenKind, lexer};
|
use ruff_python_ast::token::TokenKind;
|
||||||
|
use ruff_python_parser::{Mode, lexer};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
|||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY310,
|
python_version: PythonVersion::PY310,
|
||||||
},
|
},
|
||||||
600,
|
1070,
|
||||||
);
|
);
|
||||||
|
|
||||||
static FREQTRADE: Benchmark = Benchmark::new(
|
static FREQTRADE: Benchmark = Benchmark::new(
|
||||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
|||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
13000,
|
13100,
|
||||||
);
|
);
|
||||||
|
|
||||||
static TANJUN: Benchmark = Benchmark::new(
|
static TANJUN: Benchmark = Benchmark::new(
|
||||||
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
|||||||
max_dep_date: "2025-08-09",
|
max_dep_date: "2025-08-09",
|
||||||
python_version: PythonVersion::PY311,
|
python_version: PythonVersion::PY311,
|
||||||
},
|
},
|
||||||
900,
|
1100,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|||||||
@@ -166,28 +166,8 @@ impl Diagnostic {
|
|||||||
/// Returns the primary message for this diagnostic.
|
/// Returns the primary message for this diagnostic.
|
||||||
///
|
///
|
||||||
/// A diagnostic always has a message, but it may be empty.
|
/// A diagnostic always has a message, but it may be empty.
|
||||||
///
|
|
||||||
/// NOTE: At present, this routine will return the first primary
|
|
||||||
/// annotation's message as the primary message when the main diagnostic
|
|
||||||
/// message is empty. This is meant to facilitate an incremental migration
|
|
||||||
/// in ty over to the new diagnostic data model. (The old data model
|
|
||||||
/// didn't distinguish between messages on the entire diagnostic and
|
|
||||||
/// messages attached to a particular span.)
|
|
||||||
pub fn primary_message(&self) -> &str {
|
pub fn primary_message(&self) -> &str {
|
||||||
if !self.inner.message.as_str().is_empty() {
|
self.inner.message.as_str()
|
||||||
return self.inner.message.as_str();
|
|
||||||
}
|
|
||||||
// FIXME: As a special case, while we're migrating ty
|
|
||||||
// to the new diagnostic data model, we'll look for a primary
|
|
||||||
// message from the primary annotation. This is because most
|
|
||||||
// ty diagnostics are created with an empty diagnostic
|
|
||||||
// message and instead attach the message to the annotation.
|
|
||||||
// Fixing this will require touching basically every diagnostic
|
|
||||||
// in ty, so we do it this way for now to match the old
|
|
||||||
// semantics. ---AG
|
|
||||||
self.primary_annotation()
|
|
||||||
.and_then(|ann| ann.get_message())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Introspects this diagnostic and returns what kind of "primary" message
|
/// Introspects this diagnostic and returns what kind of "primary" message
|
||||||
@@ -199,18 +179,6 @@ impl Diagnostic {
|
|||||||
/// contains *essential* information or context for understanding the
|
/// contains *essential* information or context for understanding the
|
||||||
/// diagnostic.
|
/// diagnostic.
|
||||||
///
|
///
|
||||||
/// The reason why we don't just always return both the main diagnostic
|
|
||||||
/// message and the primary annotation message is because this was written
|
|
||||||
/// in the midst of an incremental migration of ty over to the new
|
|
||||||
/// diagnostic data model. At time of writing, diagnostics were still
|
|
||||||
/// constructed in the old model where the main diagnostic message and the
|
|
||||||
/// primary annotation message were not distinguished from each other. So
|
|
||||||
/// for now, we carefully return what kind of messages this diagnostic
|
|
||||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
|
||||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
|
||||||
/// 100% using the new diagnostic data model and we can format things
|
|
||||||
/// appropriately.
|
|
||||||
///
|
|
||||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||||
/// cases, just converting it to a string (or printing it) will do what
|
/// cases, just converting it to a string (or printing it) will do what
|
||||||
/// you want.
|
/// you want.
|
||||||
@@ -224,11 +192,10 @@ impl Diagnostic {
|
|||||||
.primary_annotation()
|
.primary_annotation()
|
||||||
.and_then(|ann| ann.get_message())
|
.and_then(|ann| ann.get_message())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match (main.is_empty(), annotation.is_empty()) {
|
if annotation.is_empty() {
|
||||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
ConciseMessage::MainDiagnostic(main)
|
||||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
} else {
|
||||||
(false, false) => ConciseMessage::Both { main, annotation },
|
ConciseMessage::Both { main, annotation }
|
||||||
(true, true) => ConciseMessage::Empty,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +321,13 @@ impl Diagnostic {
|
|||||||
Arc::make_mut(&mut self.inner).fix = Some(fix);
|
Arc::make_mut(&mut self.inner).fix = Some(fix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `fix` is `Some`, set the fix for this diagnostic.
|
||||||
|
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
|
||||||
|
if let Some(fix) = fix {
|
||||||
|
self.set_fix(fix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove the fix for this diagnostic.
|
/// Remove the fix for this diagnostic.
|
||||||
pub fn remove_fix(&mut self) {
|
pub fn remove_fix(&mut self) {
|
||||||
Arc::make_mut(&mut self.inner).fix = None;
|
Arc::make_mut(&mut self.inner).fix = None;
|
||||||
@@ -686,18 +660,6 @@ impl SubDiagnostic {
|
|||||||
/// contains *essential* information or context for understanding the
|
/// contains *essential* information or context for understanding the
|
||||||
/// diagnostic.
|
/// diagnostic.
|
||||||
///
|
///
|
||||||
/// The reason why we don't just always return both the main diagnostic
|
|
||||||
/// message and the primary annotation message is because this was written
|
|
||||||
/// in the midst of an incremental migration of ty over to the new
|
|
||||||
/// diagnostic data model. At time of writing, diagnostics were still
|
|
||||||
/// constructed in the old model where the main diagnostic message and the
|
|
||||||
/// primary annotation message were not distinguished from each other. So
|
|
||||||
/// for now, we carefully return what kind of messages this diagnostic
|
|
||||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
|
||||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
|
||||||
/// 100% using the new diagnostic data model and we can format things
|
|
||||||
/// appropriately.
|
|
||||||
///
|
|
||||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||||
/// cases, just converting it to a string (or printing it) will do what
|
/// cases, just converting it to a string (or printing it) will do what
|
||||||
/// you want.
|
/// you want.
|
||||||
@@ -707,11 +669,10 @@ impl SubDiagnostic {
|
|||||||
.primary_annotation()
|
.primary_annotation()
|
||||||
.and_then(|ann| ann.get_message())
|
.and_then(|ann| ann.get_message())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match (main.is_empty(), annotation.is_empty()) {
|
if annotation.is_empty() {
|
||||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
ConciseMessage::MainDiagnostic(main)
|
||||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
} else {
|
||||||
(false, false) => ConciseMessage::Both { main, annotation },
|
ConciseMessage::Both { main, annotation }
|
||||||
(true, true) => ConciseMessage::Empty,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -881,6 +842,10 @@ impl Annotation {
|
|||||||
pub fn hide_snippet(&mut self, yes: bool) {
|
pub fn hide_snippet(&mut self, yes: bool) {
|
||||||
self.hide_snippet = yes;
|
self.hide_snippet = yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_primary(&self) -> bool {
|
||||||
|
self.is_primary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tags that can be associated with an annotation.
|
/// Tags that can be associated with an annotation.
|
||||||
@@ -1501,28 +1466,10 @@ pub enum DiagnosticFormat {
|
|||||||
pub enum ConciseMessage<'a> {
|
pub enum ConciseMessage<'a> {
|
||||||
/// A diagnostic contains a non-empty main message and an empty
|
/// A diagnostic contains a non-empty main message and an empty
|
||||||
/// primary annotation message.
|
/// primary annotation message.
|
||||||
///
|
|
||||||
/// This strongly suggests that the diagnostic is using the
|
|
||||||
/// "new" data model.
|
|
||||||
MainDiagnostic(&'a str),
|
MainDiagnostic(&'a str),
|
||||||
/// A diagnostic contains an empty main message and a non-empty
|
|
||||||
/// primary annotation message.
|
|
||||||
///
|
|
||||||
/// This strongly suggests that the diagnostic is using the
|
|
||||||
/// "old" data model.
|
|
||||||
PrimaryAnnotation(&'a str),
|
|
||||||
/// A diagnostic contains a non-empty main message and a non-empty
|
/// A diagnostic contains a non-empty main message and a non-empty
|
||||||
/// primary annotation message.
|
/// primary annotation message.
|
||||||
///
|
|
||||||
/// This strongly suggests that the diagnostic is using the
|
|
||||||
/// "new" data model.
|
|
||||||
Both { main: &'a str, annotation: &'a str },
|
Both { main: &'a str, annotation: &'a str },
|
||||||
/// A diagnostic contains an empty main message and an empty
|
|
||||||
/// primary annotation message.
|
|
||||||
///
|
|
||||||
/// This indicates that the diagnostic is probably using the old
|
|
||||||
/// model.
|
|
||||||
Empty,
|
|
||||||
/// A custom concise message has been provided.
|
/// A custom concise message has been provided.
|
||||||
Custom(&'a str),
|
Custom(&'a str),
|
||||||
}
|
}
|
||||||
@@ -1533,13 +1480,9 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
|||||||
ConciseMessage::MainDiagnostic(main) => {
|
ConciseMessage::MainDiagnostic(main) => {
|
||||||
write!(f, "{main}")
|
write!(f, "{main}")
|
||||||
}
|
}
|
||||||
ConciseMessage::PrimaryAnnotation(annotation) => {
|
|
||||||
write!(f, "{annotation}")
|
|
||||||
}
|
|
||||||
ConciseMessage::Both { main, annotation } => {
|
ConciseMessage::Both { main, annotation } => {
|
||||||
write!(f, "{main}: {annotation}")
|
write!(f, "{main}: {annotation}")
|
||||||
}
|
}
|
||||||
ConciseMessage::Empty => Ok(()),
|
|
||||||
ConciseMessage::Custom(message) => {
|
ConciseMessage::Custom(message) => {
|
||||||
write!(f, "{message}")
|
write!(f, "{message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ use crate::source::source_text;
|
|||||||
/// reflected in the changed AST offsets.
|
/// reflected in the changed AST offsets.
|
||||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||||
/// for determining if a query result is unchanged.
|
/// for determining if a query result is unchanged.
|
||||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
|
///
|
||||||
|
/// The LRU capacity of 200 was picked without any empirical evidence that it's optimal,
|
||||||
|
/// instead it's a wild guess that it should be unlikely that incremental changes involve
|
||||||
|
/// more than 200 modules. Parsed ASTs within the same revision are never evicted by Salsa.
|
||||||
|
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size, lru=200)]
|
||||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||||
|
|
||||||
@@ -92,14 +96,9 @@ impl ParsedModule {
|
|||||||
self.inner.store(None);
|
self.inner.store(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the pointer address of this [`ParsedModule`].
|
/// Returns the file to which this module belongs.
|
||||||
///
|
pub fn file(&self) -> File {
|
||||||
/// The pointer uniquely identifies the module within the current Salsa revision,
|
self.file
|
||||||
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
|
|
||||||
pub fn addr(&self) -> usize {
|
|
||||||
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
|
|
||||||
// `Arc` within the `ArcSwap` may change.
|
|
||||||
Arc::as_ptr(&self.inner).addr()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -667,6 +667,13 @@ impl Deref for SystemPathBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for SystemPathBuf {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.0.as_std_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
|
impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
|
||||||
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
|
||||||
let mut buf = SystemPathBuf::new();
|
let mut buf = SystemPathBuf::new();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use glob::PatternError;
|
use glob::PatternError;
|
||||||
use ruff_notebook::{Notebook, NotebookError};
|
use ruff_notebook::{Notebook, NotebookError};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -20,18 +21,44 @@ use super::walk_directory::WalkDirectoryBuilder;
|
|||||||
///
|
///
|
||||||
/// ## Warning
|
/// ## Warning
|
||||||
/// Don't use this system for production code. It's intended for testing only.
|
/// Don't use this system for production code. It's intended for testing only.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct TestSystem {
|
pub struct TestSystem {
|
||||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||||
|
/// Environment variable overrides. If a key is present here, it takes precedence
|
||||||
|
/// over the inner system's environment variables.
|
||||||
|
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TestSystem {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
env_overrides: self.env_overrides.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestSystem {
|
impl TestSystem {
|
||||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(inner),
|
inner: Arc::new(inner),
|
||||||
|
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an environment variable override. This takes precedence over the inner system.
|
||||||
|
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
||||||
|
self.env_overrides
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(name.into(), Some(value.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an environment variable override, making it appear as not set.
|
||||||
|
pub fn remove_env_var(&self, name: impl Into<String>) {
|
||||||
|
self.env_overrides.lock().unwrap().insert(name.into(), None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`InMemorySystem`].
|
/// Returns the [`InMemorySystem`].
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
@@ -147,6 +174,18 @@ impl System for TestSystem {
|
|||||||
self.system().case_sensitivity()
|
self.system().case_sensitivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||||
|
// Check overrides first
|
||||||
|
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
||||||
|
return match override_value {
|
||||||
|
Some(value) => Ok(value.clone()),
|
||||||
|
None => Err(std::env::VarError::NotPresent),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Fall back to inner system
|
||||||
|
self.system().env_var(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn dyn_clone(&self) -> Box<dyn System> {
|
fn dyn_clone(&self) -> Box<dyn System> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
@@ -156,6 +195,7 @@ impl Default for TestSystem {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(InMemorySystem::default()),
|
inner: Arc::new(InMemorySystem::default()),
|
||||||
|
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
|
|
||||||
if let Some(deprecated) = &field.deprecated {
|
if let Some(deprecated) = &field.deprecated {
|
||||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
output.push_str("!!! warning \"Deprecated\"\n");
|
||||||
output.push_str("> This option has been deprecated");
|
output.push_str(" This option has been deprecated");
|
||||||
|
|
||||||
if let Some(since) = deprecated.since {
|
if let Some(since) = deprecated.since {
|
||||||
write!(output, " in {since}").unwrap();
|
write!(output, " in {since}").unwrap();
|
||||||
@@ -166,8 +166,9 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
output.push_str("**Example usage**:\n\n");
|
||||||
output.push_str(&format_example(
|
output.push_str(&format_example(
|
||||||
|
"pyproject.toml",
|
||||||
&format_header(
|
&format_header(
|
||||||
field.scope,
|
field.scope,
|
||||||
field.example,
|
field.example,
|
||||||
@@ -179,11 +180,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_example(header: &str, content: &str) -> String {
|
fn format_example(title: &str, header: &str, content: &str) -> String {
|
||||||
if header.is_empty() {
|
if header.is_empty() {
|
||||||
format!("```toml\n{content}\n```\n",)
|
format!("```toml title=\"{title}\"\n{content}\n```\n",)
|
||||||
} else {
|
} else {
|
||||||
format!("```toml\n{header}\n{content}\n```\n",)
|
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ fn generate_markdown() -> String {
|
|||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
&mut output,
|
&mut output,
|
||||||
r#"<small>
|
r#"<small>
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||||
{status_text} ·
|
{status_text} ·
|
||||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl Edit {
|
|||||||
|
|
||||||
/// Creates an edit that replaces the content in `range` with `content`.
|
/// Creates an edit that replaces the content in `range` with `content`.
|
||||||
pub fn range_replacement(content: String, range: TextRange) -> Self {
|
pub fn range_replacement(content: String, range: TextRange) -> Self {
|
||||||
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
|
debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
content: Some(Box::from(content)),
|
content: Some(Box::from(content)),
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ impl Fix {
|
|||||||
&self.edits
|
&self.edits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_edits(self) -> Vec<Edit> {
|
||||||
|
self.edits
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the [`Applicability`] of the [`Fix`].
|
/// Return the [`Applicability`] of the [`Fix`].
|
||||||
pub fn applicability(&self) -> Applicability {
|
pub fn applicability(&self) -> Applicability {
|
||||||
self.applicability
|
self.applicability
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ macro_rules! best_fitting {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer, write};
|
use crate::{FormatState, SimpleFormatOptions, VecBuffer};
|
||||||
|
|
||||||
struct TestFormat;
|
struct TestFormat;
|
||||||
|
|
||||||
@@ -385,8 +385,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn best_fitting_variants_print_as_lists() {
|
fn best_fitting_variants_print_as_lists() {
|
||||||
|
use crate::Formatted;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{Formatted, format, format_args};
|
|
||||||
|
|
||||||
// The second variant below should be selected when printing at a width of 30
|
// The second variant below should be selected when printing at a width of 30
|
||||||
let formatted_best_fitting = format!(
|
let formatted_best_fitting = format!(
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl ModuleImports {
|
|||||||
// Resolve the imports.
|
// Resolve the imports.
|
||||||
let mut resolved_imports = ModuleImports::default();
|
let mut resolved_imports = ModuleImports::default();
|
||||||
for import in imports {
|
for import in imports {
|
||||||
for resolved in Resolver::new(db).resolve(import) {
|
for resolved in Resolver::new(db, path).resolve(import) {
|
||||||
if let Some(path) = resolved.as_system_path() {
|
if let Some(path) = resolved.as_system_path() {
|
||||||
resolved_imports.insert(path.to_path_buf());
|
resolved_imports.insert(path.to_path_buf());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
use ruff_db::files::FilePath;
|
use ruff_db::files::{File, FilePath, system_path_to_file};
|
||||||
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
|
use ruff_db::system::SystemPath;
|
||||||
|
use ty_python_semantic::{
|
||||||
|
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
|
||||||
|
resolve_real_module_confident,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ModuleDb;
|
use crate::ModuleDb;
|
||||||
use crate::collector::CollectedImport;
|
use crate::collector::CollectedImport;
|
||||||
@@ -7,12 +11,15 @@ use crate::collector::CollectedImport;
|
|||||||
/// Collect all imports for a given Python file.
|
/// Collect all imports for a given Python file.
|
||||||
pub(crate) struct Resolver<'a> {
|
pub(crate) struct Resolver<'a> {
|
||||||
db: &'a ModuleDb,
|
db: &'a ModuleDb,
|
||||||
|
file: Option<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Resolver<'a> {
|
impl<'a> Resolver<'a> {
|
||||||
/// Initialize a [`Resolver`] with a given [`ModuleDb`].
|
/// Initialize a [`Resolver`] with a given [`ModuleDb`].
|
||||||
pub(crate) fn new(db: &'a ModuleDb) -> Self {
|
pub(crate) fn new(db: &'a ModuleDb, path: &SystemPath) -> Self {
|
||||||
Self { db }
|
// If we know the importing file we can potentially resolve more imports
|
||||||
|
let file = system_path_to_file(db, path).ok();
|
||||||
|
Self { db, file }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
||||||
@@ -70,13 +77,21 @@ impl<'a> Resolver<'a> {
|
|||||||
|
|
||||||
/// Resolves a module name to a module.
|
/// Resolves a module name to a module.
|
||||||
pub(crate) fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
pub(crate) fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||||
let module = resolve_module(self.db, module_name)?;
|
let module = if let Some(file) = self.file {
|
||||||
|
resolve_module(self.db, file, module_name)?
|
||||||
|
} else {
|
||||||
|
resolve_module_confident(self.db, module_name)?
|
||||||
|
};
|
||||||
Some(module.file(self.db)?.path(self.db))
|
Some(module.file(self.db)?.path(self.db))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves a module name to a module (stubs not allowed).
|
/// Resolves a module name to a module (stubs not allowed).
|
||||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||||
let module = resolve_real_module(self.db, module_name)?;
|
let module = if let Some(file) = self.file {
|
||||||
|
resolve_real_module(self.db, file, module_name)?
|
||||||
|
} else {
|
||||||
|
resolve_real_module_confident(self.db, module_name)?
|
||||||
|
};
|
||||||
Some(module.file(self.db)?.path(self.db))
|
Some(module.file(self.db)?.path(self.db))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.14.6"
|
version = "0.14.10"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -35,6 +35,7 @@ anyhow = { workspace = true }
|
|||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
||||||
colored = { workspace = true }
|
colored = { workspace = true }
|
||||||
|
compact_str = { workspace = true }
|
||||||
fern = { workspace = true }
|
fern = { workspace = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
globset = { workspace = true }
|
globset = { workspace = true }
|
||||||
|
|||||||
@@ -45,3 +45,22 @@ urllib.request.urlopen(urllib.request.Request(url))
|
|||||||
# https://github.com/astral-sh/ruff/issues/15522
|
# https://github.com/astral-sh/ruff/issues/15522
|
||||||
map(urllib.request.urlopen, [])
|
map(urllib.request.urlopen, [])
|
||||||
foo = urllib.request.urlopen
|
foo = urllib.request.urlopen
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/21462
|
||||||
|
path = "https://example.com/data.csv"
|
||||||
|
urllib.request.urlretrieve(path, "data.csv")
|
||||||
|
url = "https://example.com/api"
|
||||||
|
urllib.request.Request(url)
|
||||||
|
|
||||||
|
# Test resolved f-strings and concatenated string literals
|
||||||
|
fstring_url = f"https://example.com/data.csv"
|
||||||
|
urllib.request.urlopen(fstring_url)
|
||||||
|
urllib.request.Request(fstring_url)
|
||||||
|
|
||||||
|
concatenated_url = "https://" + "example.com/data.csv"
|
||||||
|
urllib.request.urlopen(concatenated_url)
|
||||||
|
urllib.request.Request(concatenated_url)
|
||||||
|
|
||||||
|
nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
|
urllib.request.urlopen(nested_concatenated)
|
||||||
|
urllib.request.Request(nested_concatenated)
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ yaml.load("{}", SafeLoader)
|
|||||||
yaml.load("{}", yaml.SafeLoader)
|
yaml.load("{}", yaml.SafeLoader)
|
||||||
yaml.load("{}", CSafeLoader)
|
yaml.load("{}", CSafeLoader)
|
||||||
yaml.load("{}", yaml.CSafeLoader)
|
yaml.load("{}", yaml.CSafeLoader)
|
||||||
|
yaml.load("{}", yaml.cyaml.CSafeLoader)
|
||||||
yaml.load("{}", NewSafeLoader)
|
yaml.load("{}", NewSafeLoader)
|
||||||
yaml.load("{}", Loader=SafeLoader)
|
yaml.load("{}", Loader=SafeLoader)
|
||||||
yaml.load("{}", Loader=yaml.SafeLoader)
|
yaml.load("{}", Loader=yaml.SafeLoader)
|
||||||
yaml.load("{}", Loader=CSafeLoader)
|
yaml.load("{}", Loader=CSafeLoader)
|
||||||
yaml.load("{}", Loader=yaml.CSafeLoader)
|
yaml.load("{}", Loader=yaml.CSafeLoader)
|
||||||
|
yaml.load("{}", Loader=yaml.cyaml.CSafeLoader)
|
||||||
yaml.load("{}", Loader=NewSafeLoader)
|
yaml.load("{}", Loader=NewSafeLoader)
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ def bytes_okay(value=bytes(1)):
|
|||||||
def int_okay(value=int("12")):
|
def int_okay(value=int("12")):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Allow immutable slice()
|
||||||
|
def slice_okay(value=slice(1,2)):
|
||||||
|
pass
|
||||||
|
|
||||||
# Allow immutable complex() value
|
# Allow immutable complex() value
|
||||||
def complex_okay(value=complex(1,2)):
|
def complex_okay(value=complex(1,2)):
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ def not_broken5():
|
|||||||
yield inner()
|
yield inner()
|
||||||
|
|
||||||
|
|
||||||
def not_broken6():
|
def broken3():
|
||||||
return (yield from [])
|
return (yield from [])
|
||||||
|
|
||||||
|
|
||||||
def not_broken7():
|
def broken4():
|
||||||
x = yield from []
|
x = yield from []
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
def not_broken8():
|
def broken5():
|
||||||
x = None
|
x = None
|
||||||
|
|
||||||
def inner(ex):
|
def inner(ex):
|
||||||
@@ -76,3 +76,13 @@ class NotBroken9(object):
|
|||||||
def __await__(self):
|
def __await__(self):
|
||||||
yield from function()
|
yield from function()
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
|
|
||||||
|
async def broken6():
|
||||||
|
yield 1
|
||||||
|
return foo()
|
||||||
|
|
||||||
|
|
||||||
|
async def broken7():
|
||||||
|
yield 1
|
||||||
|
return [1, 2, 3]
|
||||||
|
|||||||
66
crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC004.py
vendored
Normal file
66
crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC004.py
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
facts = (
|
||||||
|
"Lobsters have blue blood.",
|
||||||
|
"The liver is the only human organ that can fully regenerate itself.",
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||||
|
)
|
||||||
|
|
||||||
|
facts = [
|
||||||
|
"Lobsters have blue blood.",
|
||||||
|
"The liver is the only human organ that can fully regenerate itself.",
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||||
|
]
|
||||||
|
|
||||||
|
facts = {
|
||||||
|
"Lobsters have blue blood.",
|
||||||
|
"The liver is the only human organ that can fully regenerate itself.",
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||||
|
}
|
||||||
|
|
||||||
|
facts = {
|
||||||
|
(
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
facts = (
|
||||||
|
"Octopuses have three hearts."
|
||||||
|
# Missing comma here.
|
||||||
|
"Honey never spoils.",
|
||||||
|
)
|
||||||
|
|
||||||
|
facts = [
|
||||||
|
"Octopuses have three hearts."
|
||||||
|
# Missing comma here.
|
||||||
|
"Honey never spoils.",
|
||||||
|
]
|
||||||
|
|
||||||
|
facts = {
|
||||||
|
"Octopuses have three hearts."
|
||||||
|
# Missing comma here.
|
||||||
|
"Honey never spoils.",
|
||||||
|
}
|
||||||
|
|
||||||
|
facts = (
|
||||||
|
(
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
facts = [
|
||||||
|
(
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
facts = (
|
||||||
|
"Lobsters have blue blood.\n"
|
||||||
|
"The liver is the only human organ that can fully regenerate itself.\n"
|
||||||
|
"Clarinets are made almost entirely out of wood from the mpingo tree.\n"
|
||||||
|
"In 1971, astronaut Alan Shepard played golf on the moon.\n"
|
||||||
|
)
|
||||||
@@ -9,3 +9,15 @@ def test_ok():
|
|||||||
def test_error():
|
def test_error():
|
||||||
with pytest.raises(UnicodeError):
|
with pytest.raises(UnicodeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_match_only():
|
||||||
|
with pytest.raises(match="some error message"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_check_only():
|
||||||
|
with pytest.raises(check=lambda e: True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_match_and_check():
|
||||||
|
with pytest.raises(match="some error message", check=lambda e: True):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -216,3 +216,15 @@ def get_items_list():
|
|||||||
|
|
||||||
def get_items_set():
|
def get_items_set():
|
||||||
return tuple({item for item in items}) or None # OK
|
return tuple({item for item in items}) or None # OK
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/21473
|
||||||
|
tuple("") or True # SIM222
|
||||||
|
tuple(t"") or True # OK
|
||||||
|
tuple(0) or True # OK
|
||||||
|
tuple(1) or True # OK
|
||||||
|
tuple(False) or True # OK
|
||||||
|
tuple(None) or True # OK
|
||||||
|
tuple(...) or True # OK
|
||||||
|
tuple(lambda x: x) or True # OK
|
||||||
|
tuple(x for x in range(0)) or True # OK
|
||||||
|
|||||||
@@ -157,3 +157,15 @@ print(f"{1}{''}" and "bar")
|
|||||||
|
|
||||||
# https://github.com/astral-sh/ruff/issues/7127
|
# https://github.com/astral-sh/ruff/issues/7127
|
||||||
def f(a: "'' and 'b'"): ...
|
def f(a: "'' and 'b'"): ...
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/21473
|
||||||
|
tuple("") and False # SIM223
|
||||||
|
tuple(t"") and False # OK
|
||||||
|
tuple(0) and False # OK
|
||||||
|
tuple(1) and False # OK
|
||||||
|
tuple(False) and False # OK
|
||||||
|
tuple(None) and False # OK
|
||||||
|
tuple(...) and False # OK
|
||||||
|
tuple(lambda x: x) and False # OK
|
||||||
|
tuple(x for x in range(0)) and False # OK
|
||||||
|
|||||||
@@ -136,4 +136,38 @@ os.chmod("pth1_file", 0o700, None, True, 1, *[1], **{"x": 1}, foo=1)
|
|||||||
os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1)
|
os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||||
os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1)
|
os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||||
|
|
||||||
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||||
|
|
||||||
|
# See: https://github.com/astral-sh/ruff/issues/21794
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if os.rename("pth1.py", "pth1.py.bak"):
|
||||||
|
print("rename: truthy")
|
||||||
|
else:
|
||||||
|
print("rename: falsey")
|
||||||
|
|
||||||
|
if os.replace("pth1.py.bak", "pth1.py"):
|
||||||
|
print("replace: truthy")
|
||||||
|
else:
|
||||||
|
print("replace: falsey")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _ in os.getcwd():
|
||||||
|
print("getcwd: iterable")
|
||||||
|
break
|
||||||
|
except TypeError as e:
|
||||||
|
print("getcwd: not iterable")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _ in os.getcwdb():
|
||||||
|
print("getcwdb: iterable")
|
||||||
|
break
|
||||||
|
except TypeError as e:
|
||||||
|
print("getcwdb: not iterable")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _ in os.readlink(sys.executable):
|
||||||
|
print("readlink: iterable")
|
||||||
|
break
|
||||||
|
except TypeError as e:
|
||||||
|
print("readlink: not iterable")
|
||||||
|
|||||||
@@ -218,3 +218,26 @@ def should_not_fail(payload, Args):
|
|||||||
Args:
|
Args:
|
||||||
The other arguments.
|
The other arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Test cases for Unpack[TypedDict] kwargs
|
||||||
|
from typing import TypedDict
|
||||||
|
from typing_extensions import Unpack
|
||||||
|
|
||||||
|
class User(TypedDict):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def function_with_unpack_args_should_not_fail(query: str, **kwargs: Unpack[User]):
|
||||||
|
"""Function with Unpack kwargs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: some arg
|
||||||
|
"""
|
||||||
|
|
||||||
|
def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
|
||||||
|
"""Function with Unpack kwargs but missing query arg documentation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: keyword arguments
|
||||||
|
"""
|
||||||
|
|||||||
@@ -17,3 +17,24 @@ def _():
|
|||||||
|
|
||||||
# Valid yield scope
|
# Valid yield scope
|
||||||
yield 3
|
yield 3
|
||||||
|
|
||||||
|
|
||||||
|
# await is valid in any generator, sync or async
|
||||||
|
(await cor async for cor in f()) # ok
|
||||||
|
(await cor for cor in f()) # ok
|
||||||
|
|
||||||
|
# but not in comprehensions
|
||||||
|
[await cor async for cor in f()] # F704
|
||||||
|
{await cor async for cor in f()} # F704
|
||||||
|
{await cor: 1 async for cor in f()} # F704
|
||||||
|
[await cor for cor in f()] # F704
|
||||||
|
{await cor for cor in f()} # F704
|
||||||
|
{await cor: 1 for cor in f()} # F704
|
||||||
|
|
||||||
|
# or in the iterator of an async generator, which is evaluated in the parent
|
||||||
|
# scope
|
||||||
|
(cor async for cor in await f()) # F704
|
||||||
|
(await cor async for cor in [await c for c in f()]) # F704
|
||||||
|
|
||||||
|
# this is also okay because the comprehension is within the generator scope
|
||||||
|
([await c for c in cor] async for cor in f()) # ok
|
||||||
|
|||||||
@@ -2,15 +2,40 @@ from abc import ABC, abstractmethod
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
|
||||||
|
class MyError(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MySubError(MyError):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MyValueError(ValueError):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MyUserWarning(UserWarning):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# Violation test cases with builtin errors: PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 1: Useless exception statement
|
# Test case 1: Useless exception statement
|
||||||
def func():
|
def func():
|
||||||
AssertionError("This is an assertion error") # PLW0133
|
AssertionError("This is an assertion error") # PLW0133
|
||||||
|
MyError("This is a custom error") # PLW0133
|
||||||
|
MySubError("This is a custom error") # PLW0133
|
||||||
|
MyValueError("This is a custom value error") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 2: Useless exception statement in try-except block
|
# Test case 2: Useless exception statement in try-except block
|
||||||
def func():
|
def func():
|
||||||
try:
|
try:
|
||||||
Exception("This is an exception") # PLW0133
|
Exception("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -19,6 +44,9 @@ def func():
|
|||||||
def func():
|
def func():
|
||||||
if True:
|
if True:
|
||||||
RuntimeError("This is an exception") # PLW0133
|
RuntimeError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 4: Useless exception statement in class
|
# Test case 4: Useless exception statement in class
|
||||||
@@ -26,12 +54,18 @@ def func():
|
|||||||
class Class:
|
class Class:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
TypeError("This is an exception") # PLW0133
|
TypeError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 5: Useless exception statement in function
|
# Test case 5: Useless exception statement in function
|
||||||
def func():
|
def func():
|
||||||
def inner():
|
def inner():
|
||||||
IndexError("This is an exception") # PLW0133
|
IndexError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
inner()
|
inner()
|
||||||
|
|
||||||
@@ -40,6 +74,9 @@ def func():
|
|||||||
def func():
|
def func():
|
||||||
while True:
|
while True:
|
||||||
KeyError("This is an exception") # PLW0133
|
KeyError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 7: Useless exception statement in abstract class
|
# Test case 7: Useless exception statement in abstract class
|
||||||
@@ -48,27 +85,58 @@ def func():
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def method(self):
|
def method(self):
|
||||||
NotImplementedError("This is an exception") # PLW0133
|
NotImplementedError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 8: Useless exception statement inside context manager
|
# Test case 8: Useless exception statement inside context manager
|
||||||
def func():
|
def func():
|
||||||
with suppress(AttributeError):
|
with suppress(Exception):
|
||||||
AttributeError("This is an exception") # PLW0133
|
AttributeError("This is an exception") # PLW0133
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 9: Useless exception statement in parentheses
|
# Test case 9: Useless exception statement in parentheses
|
||||||
def func():
|
def func():
|
||||||
(RuntimeError("This is an exception")) # PLW0133
|
(RuntimeError("This is an exception")) # PLW0133
|
||||||
|
(MyError("This is an exception")) # PLW0133
|
||||||
|
(MySubError("This is an exception")) # PLW0133
|
||||||
|
(MyValueError("This is an exception")) # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 10: Useless exception statement in continuation
|
# Test case 10: Useless exception statement in continuation
|
||||||
def func():
|
def func():
|
||||||
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||||
|
x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||||
|
x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||||
|
x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Test case 11: Useless warning statement
|
# Test case 11: Useless warning statement
|
||||||
def func():
|
def func():
|
||||||
UserWarning("This is an assertion error") # PLW0133
|
UserWarning("This is a user warning") # PLW0133
|
||||||
|
MyUserWarning("This is a custom user warning") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 12: Useless exception statement at module level
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||||
|
|
||||||
|
PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||||
|
|
||||||
|
MyError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
MySubError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
MyValueError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
UserWarning("This is a user warning") # PLW0133
|
||||||
|
|
||||||
|
MyUserWarning("This is a custom user warning") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
# Non-violation test cases: PLW0133
|
# Non-violation test cases: PLW0133
|
||||||
@@ -119,10 +187,3 @@ def func():
|
|||||||
def func():
|
def func():
|
||||||
with suppress(AttributeError):
|
with suppress(AttributeError):
|
||||||
raise AttributeError("This is an exception") # OK
|
raise AttributeError("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
import builtins
|
|
||||||
|
|
||||||
builtins.TypeError("still an exception even though it's an Attribute")
|
|
||||||
|
|
||||||
PythonFinalizationError("Added in Python 3.13")
|
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ async def c():
|
|||||||
# Non-errors
|
# Non-errors
|
||||||
###
|
###
|
||||||
|
|
||||||
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
|
||||||
"\N{snowman} {}".format(a)
|
"\N{snowman} {}".format(a)
|
||||||
|
|
||||||
"{".format(a)
|
"{".format(a)
|
||||||
@@ -276,3 +275,6 @@ if __name__ == "__main__":
|
|||||||
number = 0
|
number = 0
|
||||||
string = "{}".format(number := number + 1)
|
string = "{}".format(number := number + 1)
|
||||||
print(string)
|
print(string)
|
||||||
|
|
||||||
|
# Unicode escape
|
||||||
|
"\N{angle}AOB = {angle}°".format(angle=180)
|
||||||
|
|||||||
@@ -138,5 +138,6 @@ with open("file.txt", encoding="utf-8") as f:
|
|||||||
with open("file.txt", encoding="utf-8") as f:
|
with open("file.txt", encoding="utf-8") as f:
|
||||||
contents = process_contents(f.read())
|
contents = process_contents(f.read())
|
||||||
|
|
||||||
with open("file.txt", encoding="utf-8") as f:
|
with open("file1.txt", encoding="utf-8") as f:
|
||||||
contents: str = process_contents(f.read())
|
contents: str = process_contents(f.read())
|
||||||
|
|
||||||
8
crates/ruff_linter/resources/test/fixtures/refurb/FURB101_1.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/refurb/FURB101_1.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
with Path("file.txt").open() as f:
|
||||||
|
contents = f.read()
|
||||||
|
|
||||||
|
with Path("file.txt").open("r") as f:
|
||||||
|
contents = f.read()
|
||||||
26
crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py
vendored
Normal file
26
crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
with Path("file.txt").open("w") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
with Path("file.txt").open("wb") as f:
|
||||||
|
f.write(b"test")
|
||||||
|
|
||||||
|
with Path("file.txt").open(mode="w") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
with Path("file.txt").open("w", encoding="utf8") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
with Path("file.txt").open("w", errors="ignore") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
with Path(foo()).open("w") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
p = Path("file.txt")
|
||||||
|
with p.open("w") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
with Path("foo", "bar", "baz").open("w") as f:
|
||||||
|
f.write("test")
|
||||||
70
crates/ruff_linter/resources/test/fixtures/ruff/RUF066.py
vendored
Normal file
70
crates/ruff_linter/resources/test/fixtures/ruff/RUF066.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import abc
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
class User: # Test normal class properties
|
||||||
|
@property
|
||||||
|
def name(self): # ERROR: No return
|
||||||
|
f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def age(self): # OK: Returning something
|
||||||
|
return 100
|
||||||
|
|
||||||
|
def method(self): # OK: Not a property
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nested(self): # ERROR: Property itself doesn't return
|
||||||
|
def inner():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stub(self): ... # OK: A stub; doesn't return anything
|
||||||
|
|
||||||
|
|
||||||
|
class UserMeta(metaclass=abc.ABCMeta): # Test properies inside of an ABC class
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def abstr_prop1(self): ... # OK: Abstract methods doesn't need to return anything
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def abstr_prop2(self): # OK: Abstract methods doesn't need to return anything
|
||||||
|
"""
|
||||||
|
A cool docstring
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prop1(self): # OK: Returning a value
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prop2(self): # ERROR: Not returning something (even when we are inside an ABC)
|
||||||
|
50
|
||||||
|
|
||||||
|
def method(self): # OK: Not a property
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
|
||||||
|
def func(): # OK: Not a property
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Proto(typing.Protocol): # Tests for a Protocol class
|
||||||
|
@property
|
||||||
|
def prop1(self) -> int: ... # OK: A stub property
|
||||||
|
|
||||||
|
|
||||||
|
class File: # Extra tests for things like yield/yield from/raise
|
||||||
|
@property
|
||||||
|
def stream1(self): # OK: Yields something
|
||||||
|
yield
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stream2(self): # OK: Yields from something
|
||||||
|
yield from self.stream1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self): # OK: Raises
|
||||||
|
raise ValueError("File does not have children")
|
||||||
111
crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py
vendored
Normal file
111
crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
def f():
|
||||||
|
# These should both be ignored by the range suppression.
|
||||||
|
# ruff: disable[E741, F841]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741, F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# These should both be ignored by the implicit range suppression.
|
||||||
|
# Should also generate an "unmatched suppression" warning.
|
||||||
|
# ruff:disable[E741,F841]
|
||||||
|
I = 1
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Neither warning is ignored, and an "unmatched suppression"
|
||||||
|
# should be generated.
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741, F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# One should be ignored by the range suppression, and
|
||||||
|
# the other logged to the user.
|
||||||
|
# ruff: disable[E741]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Test interleaved range suppressions. The first and last
|
||||||
|
# lines should each log a different warning, while the
|
||||||
|
# middle line should be completely silenced.
|
||||||
|
# ruff: disable[E741]
|
||||||
|
l = 0
|
||||||
|
# ruff: disable[F841]
|
||||||
|
O = 1
|
||||||
|
# ruff: enable[E741]
|
||||||
|
I = 2
|
||||||
|
# ruff: enable[F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Neither of these are ignored and warnings are
|
||||||
|
# logged to user
|
||||||
|
# ruff: disable[E501]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E501]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# These should both be ignored by the range suppression,
|
||||||
|
# and an unusued noqa diagnostic should be logged.
|
||||||
|
# ruff:disable[E741,F841]
|
||||||
|
I = 1 # noqa: E741,F841
|
||||||
|
# ruff:enable[E741,F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# TODO: Duplicate codes should be counted as duplicate, not unused
|
||||||
|
# ruff: disable[F841, F841]
|
||||||
|
foo = 0
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Overlapping range suppressions, one should be marked as used,
|
||||||
|
# and the other should trigger an unused suppression diagnostic
|
||||||
|
# ruff: disable[F841]
|
||||||
|
# ruff: disable[F841]
|
||||||
|
foo = 0
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Multiple codes but only one is used
|
||||||
|
# ruff: disable[E741, F401, F841]
|
||||||
|
foo = 0
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Multiple codes but only two are used
|
||||||
|
# ruff: disable[E741, F401, F841]
|
||||||
|
I = 0
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Multiple codes but none are used
|
||||||
|
# ruff: disable[E741, F401, F841]
|
||||||
|
print("hello")
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Unknown rule codes
|
||||||
|
# ruff: disable[YF829]
|
||||||
|
# ruff: disable[F841, RQW320]
|
||||||
|
value = 0
|
||||||
|
# ruff: enable[F841, RQW320]
|
||||||
|
# ruff: enable[YF829]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# External rule codes should be ignored
|
||||||
|
# ruff: disable[TK421]
|
||||||
|
print("hello")
|
||||||
|
# ruff: enable[TK421]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Empty or missing rule codes
|
||||||
|
# ruff: disable
|
||||||
|
# ruff: disable[]
|
||||||
|
print("hello")
|
||||||
38
crates/ruff_linter/resources/test/fixtures/semantic_errors/annotated_global.py
vendored
Normal file
38
crates/ruff_linter/resources/test/fixtures/semantic_errors/annotated_global.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
a: int = 1
|
||||||
|
def f1():
|
||||||
|
global a
|
||||||
|
a: str = "foo" # error
|
||||||
|
|
||||||
|
b: int = 1
|
||||||
|
def outer():
|
||||||
|
def inner():
|
||||||
|
global b
|
||||||
|
b: str = "nested" # error
|
||||||
|
|
||||||
|
c: int = 1
|
||||||
|
def f2():
|
||||||
|
global c
|
||||||
|
c: list[str] = [] # error
|
||||||
|
|
||||||
|
d: int = 1
|
||||||
|
def f3():
|
||||||
|
global d
|
||||||
|
d: str # error
|
||||||
|
|
||||||
|
e: int = 1
|
||||||
|
def f4():
|
||||||
|
e: str = "happy" # okay
|
||||||
|
|
||||||
|
global f
|
||||||
|
f: int = 1 # okay
|
||||||
|
|
||||||
|
g: int = 1
|
||||||
|
global g # error
|
||||||
|
|
||||||
|
class C:
|
||||||
|
x: str
|
||||||
|
global x # error
|
||||||
|
|
||||||
|
class D:
|
||||||
|
global x # error
|
||||||
|
x: str
|
||||||
@@ -3,3 +3,5 @@ def func():
|
|||||||
|
|
||||||
# Top-level await
|
# Top-level await
|
||||||
await 1
|
await 1
|
||||||
|
|
||||||
|
([await c for c in cor] async for cor in func()) # ok
|
||||||
|
|||||||
24
crates/ruff_linter/resources/test/fixtures/syntax_errors/return_in_generator.py
vendored
Normal file
24
crates/ruff_linter/resources/test/fixtures/syntax_errors/return_in_generator.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
async def gen():
|
||||||
|
yield 1
|
||||||
|
return 42
|
||||||
|
|
||||||
|
def gen(): # B901 but not a syntax error - not an async generator
|
||||||
|
yield 1
|
||||||
|
return 42
|
||||||
|
|
||||||
|
async def gen(): # ok - no value in return
|
||||||
|
yield 1
|
||||||
|
return
|
||||||
|
|
||||||
|
async def gen():
|
||||||
|
yield 1
|
||||||
|
return foo()
|
||||||
|
|
||||||
|
async def gen():
|
||||||
|
yield 1
|
||||||
|
return [1, 2, 3]
|
||||||
|
|
||||||
|
async def gen():
|
||||||
|
if True:
|
||||||
|
yield 1
|
||||||
|
return 10
|
||||||
@@ -17,7 +17,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:8:5: F841 [
|
|||||||
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
||||||
crates/ruff_linter/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
crates/ruff_linter/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||||
Found 7 errors.
|
Found 7 errors.
|
||||||
[*] 7 potentially fixable with the --fix option.
|
[*] 7 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Running from the project directory itself should exhibit the same behavior:
|
Running from the project directory itself should exhibit the same behavior:
|
||||||
@@ -32,7 +32,7 @@ examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but n
|
|||||||
project/file.py:1:8: F401 [*] `os` imported but unused
|
project/file.py:1:8: F401 [*] `os` imported but unused
|
||||||
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||||
Found 7 errors.
|
Found 7 errors.
|
||||||
[*] 7 potentially fixable with the --fix option.
|
[*] 7 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
|
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
|
||||||
@@ -43,7 +43,7 @@ files:
|
|||||||
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||||
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||||
Found 2 errors.
|
Found 2 errors.
|
||||||
[*] 2 potentially fixable with the --fix option.
|
[*] 2 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
|
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
|
||||||
@@ -61,7 +61,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:4:27: F401
|
|||||||
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
||||||
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
||||||
Found 9 errors.
|
Found 9 errors.
|
||||||
[*] 9 potentially fixable with the --fix option.
|
[*] 9 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
|
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
|
||||||
@@ -74,7 +74,7 @@ docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
|||||||
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||||
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
|
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
|
||||||
Found 4 errors.
|
Found 4 errors.
|
||||||
[*] 4 potentially fixable with the --fix option.
|
[*] 4 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing an excluded directory directly should report errors in the contained files:
|
Passing an excluded directory directly should report errors in the contained files:
|
||||||
@@ -83,7 +83,7 @@ Passing an excluded directory directly should report errors in the contained fil
|
|||||||
∴ cargo run -p ruff -- check crates/ruff_linter/resources/test/project/examples/excluded/
|
∴ cargo run -p ruff -- check crates/ruff_linter/resources/test/project/examples/excluded/
|
||||||
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 potentially fixable with the --fix option.
|
[*] 1 potentially fixable with the `--fix` option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Unless we `--force-exclude`:
|
Unless we `--force-exclude`:
|
||||||
|
|||||||
@@ -214,6 +214,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
}) => {
|
}) => {
|
||||||
|
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
||||||
|
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
||||||
|
checker,
|
||||||
|
expr,
|
||||||
|
elts,
|
||||||
|
);
|
||||||
|
}
|
||||||
if ctx.is_store() {
|
if ctx.is_store() {
|
||||||
let check_too_many_expressions =
|
let check_too_many_expressions =
|
||||||
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
||||||
@@ -1329,6 +1336,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Set(set) => {
|
Expr::Set(set) => {
|
||||||
|
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
||||||
|
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
||||||
|
checker,
|
||||||
|
expr,
|
||||||
|
&set.elts,
|
||||||
|
);
|
||||||
|
}
|
||||||
if checker.is_rule_enabled(Rule::DuplicateValue) {
|
if checker.is_rule_enabled(Rule::DuplicateValue) {
|
||||||
flake8_bugbear::rules::duplicate_value(checker, set);
|
flake8_bugbear::rules::duplicate_value(checker, set);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||||||
if checker.is_rule_enabled(Rule::InvalidArgumentName) {
|
if checker.is_rule_enabled(Rule::InvalidArgumentName) {
|
||||||
pep8_naming::rules::invalid_argument_name_function(checker, function_def);
|
pep8_naming::rules::invalid_argument_name_function(checker, function_def);
|
||||||
}
|
}
|
||||||
|
if checker.is_rule_enabled(Rule::PropertyWithoutReturn) {
|
||||||
|
ruff::rules::property_without_return(checker, function_def);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Return(_) => {
|
Stmt::Return(_) => {
|
||||||
if checker.is_rule_enabled(Rule::ReturnInInit) {
|
if checker.is_rule_enabled(Rule::ReturnInInit) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to
|
|||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::name::QualifiedName;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::str::Quote;
|
use ruff_python_ast::str::Quote;
|
||||||
|
use ruff_python_ast::token::Tokens;
|
||||||
use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern};
|
use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern};
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
||||||
@@ -48,7 +49,7 @@ use ruff_python_parser::semantic_errors::{
|
|||||||
SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind,
|
SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind,
|
||||||
};
|
};
|
||||||
use ruff_python_parser::typing::{AnnotationKind, ParsedAnnotation, parse_type_annotation};
|
use ruff_python_parser::typing::{AnnotationKind, ParsedAnnotation, parse_type_annotation};
|
||||||
use ruff_python_parser::{ParseError, Parsed, Tokens};
|
use ruff_python_parser::{ParseError, Parsed};
|
||||||
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
|
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
|
||||||
use ruff_python_semantic::analyze::{imports, typing};
|
use ruff_python_semantic::analyze::{imports, typing};
|
||||||
use ruff_python_semantic::{
|
use ruff_python_semantic::{
|
||||||
@@ -68,6 +69,7 @@ use crate::noqa::NoqaMapping;
|
|||||||
use crate::package::PackageRoot;
|
use crate::package::PackageRoot;
|
||||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
use crate::rules::flake8_bugbear::rules::ReturnInGenerator;
|
||||||
use crate::rules::pyflakes::rules::{
|
use crate::rules::pyflakes::rules::{
|
||||||
LateFutureImport, MultipleStarredExpressions, ReturnOutsideFunction,
|
LateFutureImport, MultipleStarredExpressions, ReturnOutsideFunction,
|
||||||
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
||||||
@@ -435,6 +437,15 @@ impl<'a> Checker<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Tokens`] for the parsed source file.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Unlike [`Self::tokens`], this method always returns
|
||||||
|
/// the tokens for the current file, even when within a parsed type annotation.
|
||||||
|
pub(crate) fn source_tokens(&self) -> &'a Tokens {
|
||||||
|
self.parsed.tokens()
|
||||||
|
}
|
||||||
|
|
||||||
/// The [`Locator`] for the current file, which enables extraction of source code from byte
|
/// The [`Locator`] for the current file, which enables extraction of source code from byte
|
||||||
/// offsets.
|
/// offsets.
|
||||||
pub(crate) const fn locator(&self) -> &'a Locator<'a> {
|
pub(crate) const fn locator(&self) -> &'a Locator<'a> {
|
||||||
@@ -728,6 +739,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
|||||||
self.report_diagnostic(NonlocalWithoutBinding { name }, error.range);
|
self.report_diagnostic(NonlocalWithoutBinding { name }, error.range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SemanticSyntaxErrorKind::ReturnInGenerator => {
|
||||||
|
// B901
|
||||||
|
if self.is_rule_enabled(Rule::ReturnInGenerator) {
|
||||||
|
self.report_diagnostic(ReturnInGenerator, error.range);
|
||||||
|
}
|
||||||
|
}
|
||||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||||
@@ -746,6 +763,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
|||||||
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
||||||
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
||||||
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
||||||
|
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
|
||||||
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
|
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
|
||||||
self.semantic_errors.borrow_mut().push(error);
|
self.semantic_errors.borrow_mut().push(error);
|
||||||
}
|
}
|
||||||
@@ -779,6 +797,10 @@ impl SemanticSyntaxContext for Checker<'_> {
|
|||||||
match scope.kind {
|
match scope.kind {
|
||||||
ScopeKind::Class(_) => return false,
|
ScopeKind::Class(_) => return false,
|
||||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||||
|
ScopeKind::Generator {
|
||||||
|
kind: GeneratorKind::Generator,
|
||||||
|
..
|
||||||
|
} => return true,
|
||||||
ScopeKind::Generator { .. }
|
ScopeKind::Generator { .. }
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
| ScopeKind::Type
|
| ScopeKind::Type
|
||||||
@@ -828,14 +850,19 @@ impl SemanticSyntaxContext for Checker<'_> {
|
|||||||
self.source_type.is_ipynb()
|
self.source_type.is_ipynb()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_generator_scope(&self) -> bool {
|
fn in_generator_context(&self) -> bool {
|
||||||
matches!(
|
for scope in self.semantic.current_scopes() {
|
||||||
&self.semantic.current_scope().kind,
|
if matches!(
|
||||||
ScopeKind::Generator {
|
scope.kind,
|
||||||
kind: GeneratorKind::Generator,
|
ScopeKind::Generator {
|
||||||
..
|
kind: GeneratorKind::Generator,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_loop_context(&self) -> bool {
|
fn in_loop_context(&self) -> bool {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_parser::{TokenKind, Tokens};
|
|
||||||
use ruff_source_file::LineRanges;
|
use ruff_source_file::LineRanges;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
|||||||
@@ -12,17 +12,20 @@ use crate::fix::edits::delete_comment;
|
|||||||
use crate::noqa::{
|
use crate::noqa::{
|
||||||
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
||||||
};
|
};
|
||||||
|
use crate::preview::is_range_suppressions_enabled;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rule_redirects::get_redirect_target;
|
||||||
use crate::rules::pygrep_hooks;
|
use crate::rules::pygrep_hooks;
|
||||||
use crate::rules::ruff;
|
use crate::rules::ruff;
|
||||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Edit, Fix, Locator};
|
use crate::{Edit, Fix, Locator};
|
||||||
|
|
||||||
use super::ast::LintContext;
|
use super::ast::LintContext;
|
||||||
|
|
||||||
/// RUF100
|
/// RUF100
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
pub(crate) fn check_noqa(
|
pub(crate) fn check_noqa(
|
||||||
context: &mut LintContext,
|
context: &mut LintContext,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
@@ -31,6 +34,7 @@ pub(crate) fn check_noqa(
|
|||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
analyze_directives: bool,
|
analyze_directives: bool,
|
||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
// Identify any codes that are globally exempted (within the current file).
|
// Identify any codes that are globally exempted (within the current file).
|
||||||
let file_noqa_directives =
|
let file_noqa_directives =
|
||||||
@@ -40,7 +44,7 @@ pub(crate) fn check_noqa(
|
|||||||
let mut noqa_directives =
|
let mut noqa_directives =
|
||||||
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
||||||
|
|
||||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
|
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +64,19 @@ pub(crate) fn check_noqa(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply file-level suppressions first
|
||||||
if exemption.contains_secondary_code(code) {
|
if exemption.contains_secondary_code(code) {
|
||||||
ignored_diagnostics.push(index);
|
ignored_diagnostics.push(index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply ranged suppressions next
|
||||||
|
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
|
||||||
|
ignored_diagnostics.push(index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply end-of-line noqa suppressions last
|
||||||
let noqa_offsets = diagnostic
|
let noqa_offsets = diagnostic
|
||||||
.parent()
|
.parent()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -107,6 +119,9 @@ pub(crate) fn check_noqa(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Diagnostics for unused/invalid range suppressions
|
||||||
|
suppressions.check_suppressions(context, locator);
|
||||||
|
|
||||||
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||||
// suppressed.
|
// suppressed.
|
||||||
if context.is_rule_enabled(Rule::UnusedNOQA)
|
if context.is_rule_enabled(Rule::UnusedNOQA)
|
||||||
@@ -128,8 +143,13 @@ pub(crate) fn check_noqa(
|
|||||||
Directive::All(directive) => {
|
Directive::All(directive) => {
|
||||||
if matches.is_empty() {
|
if matches.is_empty() {
|
||||||
let edit = delete_comment(directive.range(), locator);
|
let edit = delete_comment(directive.range(), locator);
|
||||||
let mut diagnostic = context
|
let mut diagnostic = context.report_diagnostic(
|
||||||
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
|
UnusedNOQA {
|
||||||
|
codes: None,
|
||||||
|
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||||
|
},
|
||||||
|
directive.range(),
|
||||||
|
);
|
||||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||||
}
|
}
|
||||||
@@ -224,6 +244,7 @@ pub(crate) fn check_noqa(
|
|||||||
.map(|code| (*code).to_string())
|
.map(|code| (*code).to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
}),
|
}),
|
||||||
|
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||||
},
|
},
|
||||||
directive.range(),
|
directive.range(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use std::path::Path;
|
|||||||
|
|
||||||
use ruff_notebook::CellOffsets;
|
use ruff_notebook::CellOffsets;
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::PySourceType;
|
||||||
|
use ruff_python_ast::token::Tokens;
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_parser::Tokens;
|
|
||||||
|
|
||||||
use crate::Locator;
|
use crate::Locator;
|
||||||
use crate::directives::TodoComment;
|
use crate::directives::TodoComment;
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Flake8ImplicitStrConcat, "001") => rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "001") => rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation,
|
||||||
(Flake8ImplicitStrConcat, "002") => rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "002") => rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
|
||||||
(Flake8ImplicitStrConcat, "003") => rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
|
(Flake8ImplicitStrConcat, "003") => rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
|
||||||
|
(Flake8ImplicitStrConcat, "004") => rules::flake8_implicit_str_concat::rules::ImplicitStringConcatenationInCollectionLiteral,
|
||||||
|
|
||||||
// flake8-print
|
// flake8-print
|
||||||
(Flake8Print, "1") => rules::flake8_print::rules::Print,
|
(Flake8Print, "1") => rules::flake8_print::rules::Print,
|
||||||
@@ -1058,10 +1059,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Ruff, "063") => rules::ruff::rules::AccessAnnotationsFromClassDict,
|
(Ruff, "063") => rules::ruff::rules::AccessAnnotationsFromClassDict,
|
||||||
(Ruff, "064") => rules::ruff::rules::NonOctalPermissions,
|
(Ruff, "064") => rules::ruff::rules::NonOctalPermissions,
|
||||||
(Ruff, "065") => rules::ruff::rules::LoggingEagerConversion,
|
(Ruff, "065") => rules::ruff::rules::LoggingEagerConversion,
|
||||||
|
(Ruff, "066") => rules::ruff::rules::PropertyWithoutReturn,
|
||||||
|
|
||||||
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
||||||
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
||||||
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
||||||
|
(Ruff, "103") => rules::ruff::rules::InvalidSuppressionComment,
|
||||||
|
(Ruff, "104") => rules::ruff::rules::UnmatchedSuppressionComment,
|
||||||
|
|
||||||
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
||||||
#[cfg(any(feature = "test-rules", test))]
|
#[cfg(any(feature = "test-rules", test))]
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_parser::{TokenKind, Tokens};
|
|
||||||
use ruff_python_trivia::CommentRanges;
|
use ruff_python_trivia::CommentRanges;
|
||||||
use ruff_source_file::LineRanges;
|
use ruff_source_file::LineRanges;
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::iter::FusedIterator;
|
|||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
||||||
|
use ruff_python_ast::token::{Token, TokenKind, Tokens};
|
||||||
use ruff_python_ast::{self as ast, Stmt, Suite};
|
use ruff_python_ast::{self as ast, Stmt, Suite};
|
||||||
use ruff_python_parser::{Token, TokenKind, Tokens};
|
|
||||||
use ruff_source_file::UniversalNewlineIterator;
|
use ruff_source_file::UniversalNewlineIterator;
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use ruff_python_ast::AnyNodeRef;
|
use ruff_python_ast::AnyNodeRef;
|
||||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
use ruff_python_ast::token::{self, Tokens, parenthesized_range};
|
||||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
|
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_trivia::textwrap::dedent_to;
|
use ruff_python_trivia::textwrap::dedent_to;
|
||||||
use ruff_python_trivia::{
|
use ruff_python_trivia::{
|
||||||
CommentRanges, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content,
|
PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content, is_python_whitespace,
|
||||||
is_python_whitespace,
|
|
||||||
};
|
};
|
||||||
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines};
|
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines};
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||||
@@ -209,7 +208,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
|||||||
arguments: &Arguments,
|
arguments: &Arguments,
|
||||||
parentheses: Parentheses,
|
parentheses: Parentheses,
|
||||||
source: &str,
|
source: &str,
|
||||||
comment_ranges: &CommentRanges,
|
tokens: &Tokens,
|
||||||
) -> Result<Edit> {
|
) -> Result<Edit> {
|
||||||
// Partition into arguments before and after the argument to remove.
|
// Partition into arguments before and after the argument to remove.
|
||||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||||
@@ -224,7 +223,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
|||||||
.context("Unable to find argument")?;
|
.context("Unable to find argument")?;
|
||||||
|
|
||||||
let parenthesized_range =
|
let parenthesized_range =
|
||||||
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
|
token::parenthesized_range(arg.value().into(), arguments.into(), tokens)
|
||||||
.unwrap_or(arg.range());
|
.unwrap_or(arg.range());
|
||||||
|
|
||||||
if !after.is_empty() {
|
if !after.is_empty() {
|
||||||
@@ -270,25 +269,14 @@ pub(crate) fn remove_argument<T: Ranged>(
|
|||||||
///
|
///
|
||||||
/// The new argument will be inserted before the first existing keyword argument in `arguments`, if
|
/// The new argument will be inserted before the first existing keyword argument in `arguments`, if
|
||||||
/// there are any present. Otherwise, the new argument is added to the end of the argument list.
|
/// there are any present. Otherwise, the new argument is added to the end of the argument list.
|
||||||
pub(crate) fn add_argument(
|
pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Tokens) -> Edit {
|
||||||
argument: &str,
|
|
||||||
arguments: &Arguments,
|
|
||||||
comment_ranges: &CommentRanges,
|
|
||||||
source: &str,
|
|
||||||
) -> Edit {
|
|
||||||
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
|
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
|
||||||
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
|
let keyword = parenthesized_range(value.into(), arguments.into(), tokens).unwrap_or(*range);
|
||||||
.unwrap_or(*range);
|
|
||||||
Edit::insertion(format!("{argument}, "), keyword.start())
|
Edit::insertion(format!("{argument}, "), keyword.start())
|
||||||
} else if let Some(last) = arguments.arguments_source_order().last() {
|
} else if let Some(last) = arguments.arguments_source_order().last() {
|
||||||
// Case 1: existing arguments, so append after the last argument.
|
// Case 1: existing arguments, so append after the last argument.
|
||||||
let last = parenthesized_range(
|
let last = parenthesized_range(last.value().into(), arguments.into(), tokens)
|
||||||
last.value().into(),
|
.unwrap_or(last.range());
|
||||||
arguments.into(),
|
|
||||||
comment_ranges,
|
|
||||||
source,
|
|
||||||
)
|
|
||||||
.unwrap_or(last.range());
|
|
||||||
Edit::insertion(format!(", {argument}"), last.end())
|
Edit::insertion(format!(", {argument}"), last.end())
|
||||||
} else {
|
} else {
|
||||||
// Case 2: no arguments. Add argument, without any trailing comma.
|
// Case 2: no arguments. Add argument, without any trailing comma.
|
||||||
@@ -298,12 +286,7 @@ pub(crate) fn add_argument(
|
|||||||
|
|
||||||
/// Generic function to add a (regular) parameter to a function definition.
|
/// Generic function to add a (regular) parameter to a function definition.
|
||||||
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
||||||
if let Some(last) = parameters
|
if let Some(last) = parameters.args.iter().rfind(|arg| arg.default.is_none()) {
|
||||||
.args
|
|
||||||
.iter()
|
|
||||||
.filter(|arg| arg.default.is_none())
|
|
||||||
.next_back()
|
|
||||||
{
|
|
||||||
// Case 1: at least one regular parameter, so append after the last one.
|
// Case 1: at least one regular parameter, so append after the last one.
|
||||||
Edit::insertion(format!(", {parameter}"), last.end())
|
Edit::insertion(format!(", {parameter}"), last.end())
|
||||||
} else if !parameters.args.is_empty() {
|
} else if !parameters.args.is_empty() {
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ use anyhow::Result;
|
|||||||
use libcst_native as cst;
|
use libcst_native as cst;
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
|
use ruff_python_ast::token::Tokens;
|
||||||
use ruff_python_ast::{self as ast, Expr, ModModule, Stmt};
|
use ruff_python_ast::{self as ast, Expr, ModModule, Stmt};
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_importer::Insertion;
|
use ruff_python_importer::Insertion;
|
||||||
use ruff_python_parser::{Parsed, Tokens};
|
use ruff_python_parser::Parsed;
|
||||||
use ruff_python_semantic::{
|
use ruff_python_semantic::{
|
||||||
ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel,
|
ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ pub mod rule_selector;
|
|||||||
pub mod rules;
|
pub mod rules;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod source_kind;
|
pub mod source_kind;
|
||||||
|
pub mod suppression;
|
||||||
mod text_helpers;
|
mod text_helpers;
|
||||||
pub mod upstream_categories;
|
pub mod upstream_categories;
|
||||||
mod violation;
|
mod violation;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
|
|||||||
use crate::settings::types::UnsafeFixes;
|
use crate::settings::types::UnsafeFixes;
|
||||||
use crate::settings::{LinterSettings, TargetVersion, flags};
|
use crate::settings::{LinterSettings, TargetVersion, flags};
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Locator, directives, fs};
|
use crate::{Locator, directives, fs};
|
||||||
|
|
||||||
pub(crate) mod float;
|
pub(crate) mod float;
|
||||||
@@ -128,6 +129,7 @@ pub fn check_path(
|
|||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
parsed: &Parsed<ModModule>,
|
parsed: &Parsed<ModModule>,
|
||||||
target_version: TargetVersion,
|
target_version: TargetVersion,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
// Aggregate all diagnostics.
|
// Aggregate all diagnostics.
|
||||||
let mut context = LintContext::new(path, locator.contents(), settings);
|
let mut context = LintContext::new(path, locator.contents(), settings);
|
||||||
@@ -339,6 +341,7 @@ pub fn check_path(
|
|||||||
&directives.noqa_line_for,
|
&directives.noqa_line_for,
|
||||||
parsed.has_valid_syntax(),
|
parsed.has_valid_syntax(),
|
||||||
settings,
|
settings,
|
||||||
|
suppressions,
|
||||||
);
|
);
|
||||||
if noqa.is_enabled() {
|
if noqa.is_enabled() {
|
||||||
for index in ignored.iter().rev() {
|
for index in ignored.iter().rev() {
|
||||||
@@ -400,6 +403,9 @@ pub fn add_noqa_to_path(
|
|||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics, ignoring any existing `noqa` directives.
|
// Generate diagnostics, ignoring any existing `noqa` directives.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
@@ -414,6 +420,7 @@ pub fn add_noqa_to_path(
|
|||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add any missing `# noqa` pragmas.
|
// Add any missing `# noqa` pragmas.
|
||||||
@@ -427,6 +434,7 @@ pub fn add_noqa_to_path(
|
|||||||
&directives.noqa_line_for,
|
&directives.noqa_line_for,
|
||||||
stylist.line_ending(),
|
stylist.line_ending(),
|
||||||
reason,
|
reason,
|
||||||
|
&suppressions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,6 +469,9 @@ pub fn lint_only(
|
|||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics.
|
// Generate diagnostics.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
@@ -475,6 +486,7 @@ pub fn lint_only(
|
|||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
LinterResult {
|
LinterResult {
|
||||||
@@ -566,6 +578,9 @@ pub fn lint_fix<'a>(
|
|||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics.
|
// Generate diagnostics.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
@@ -580,6 +595,7 @@ pub fn lint_fix<'a>(
|
|||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
if iterations == 0 {
|
if iterations == 0 {
|
||||||
@@ -769,6 +785,7 @@ mod tests {
|
|||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
||||||
use crate::{Locator, assert_diagnostics, directives, settings};
|
use crate::{Locator, assert_diagnostics, directives, settings};
|
||||||
|
|
||||||
@@ -944,6 +961,7 @@ mod tests {
|
|||||||
&locator,
|
&locator,
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
let mut diagnostics = check_path(
|
let mut diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
None,
|
None,
|
||||||
@@ -957,6 +975,7 @@ mod tests {
|
|||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
||||||
diagnostics
|
diagnostics
|
||||||
@@ -982,6 +1001,7 @@ mod tests {
|
|||||||
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
||||||
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
||||||
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
||||||
|
#[test_case(Path::new("annotated_global.py"), PythonVersion::PY314)]
|
||||||
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
||||||
let snapshot = format!(
|
let snapshot = format!(
|
||||||
"semantic_syntax_error_{}_{}",
|
"semantic_syntax_error_{}_{}",
|
||||||
@@ -1043,6 +1063,7 @@ mod tests {
|
|||||||
Rule::YieldFromInAsyncFunction,
|
Rule::YieldFromInAsyncFunction,
|
||||||
Path::new("yield_from_in_async_function.py")
|
Path::new("yield_from_in_async_function.py")
|
||||||
)]
|
)]
|
||||||
|
#[test_case(Rule::ReturnInGenerator, Path::new("return_in_generator.py"))]
|
||||||
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
|
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = path.to_string_lossy().to_string();
|
let snapshot = path.to_string_lossy().to_string();
|
||||||
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
|
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ use crate::Locator;
|
|||||||
use crate::fs::relativize_path;
|
use crate::fs::relativize_path;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rule_redirects::get_redirect_target;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
|
|
||||||
/// Generates an array of edits that matches the length of `messages`.
|
/// Generates an array of edits that matches the length of `messages`.
|
||||||
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
||||||
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
||||||
/// the diagnostic. These edits may conflict with each other and should not be applied
|
/// the diagnostic. These edits may conflict with each other and should not be applied
|
||||||
/// simultaneously.
|
/// simultaneously.
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
pub fn generate_noqa_edits(
|
pub fn generate_noqa_edits(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
diagnostics: &[Diagnostic],
|
diagnostics: &[Diagnostic],
|
||||||
@@ -34,11 +36,19 @@ pub fn generate_noqa_edits(
|
|||||||
external: &[String],
|
external: &[String],
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<Option<Edit>> {
|
) -> Vec<Option<Edit>> {
|
||||||
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
||||||
let exemption = FileExemption::from(&file_directives);
|
let exemption = FileExemption::from(&file_directives);
|
||||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
let comments = find_noqa_comments(
|
||||||
|
diagnostics,
|
||||||
|
locator,
|
||||||
|
&exemption,
|
||||||
|
&directives,
|
||||||
|
noqa_line_for,
|
||||||
|
suppressions,
|
||||||
|
);
|
||||||
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,6 +735,7 @@ pub(crate) fn add_noqa(
|
|||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
reason: Option<&str>,
|
reason: Option<&str>,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let (count, output) = add_noqa_inner(
|
let (count, output) = add_noqa_inner(
|
||||||
path,
|
path,
|
||||||
@@ -735,6 +746,7 @@ pub(crate) fn add_noqa(
|
|||||||
noqa_line_for,
|
noqa_line_for,
|
||||||
line_ending,
|
line_ending,
|
||||||
reason,
|
reason,
|
||||||
|
suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::write(path, output)?;
|
fs::write(path, output)?;
|
||||||
@@ -751,6 +763,7 @@ fn add_noqa_inner(
|
|||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
reason: Option<&str>,
|
reason: Option<&str>,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> (usize, String) {
|
) -> (usize, String) {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
@@ -760,7 +773,14 @@ fn add_noqa_inner(
|
|||||||
|
|
||||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||||
|
|
||||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
let comments = find_noqa_comments(
|
||||||
|
diagnostics,
|
||||||
|
locator,
|
||||||
|
&exemption,
|
||||||
|
&directives,
|
||||||
|
noqa_line_for,
|
||||||
|
suppressions,
|
||||||
|
);
|
||||||
|
|
||||||
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
||||||
|
|
||||||
@@ -859,6 +879,7 @@ fn find_noqa_comments<'a>(
|
|||||||
exemption: &'a FileExemption,
|
exemption: &'a FileExemption,
|
||||||
directives: &'a NoqaDirectives,
|
directives: &'a NoqaDirectives,
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
|
suppressions: &'a Suppressions,
|
||||||
) -> Vec<Option<NoqaComment<'a>>> {
|
) -> Vec<Option<NoqaComment<'a>>> {
|
||||||
// List of noqa comments, ordered to match up with `messages`
|
// List of noqa comments, ordered to match up with `messages`
|
||||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||||
@@ -875,6 +896,12 @@ fn find_noqa_comments<'a>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply ranged suppressions next
|
||||||
|
if suppressions.check_diagnostic(message) {
|
||||||
|
comments_by_line.push(None);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||||
if let Some(parent) = message.parent() {
|
if let Some(parent) = message.parent() {
|
||||||
if let Some(directive_line) =
|
if let Some(directive_line) =
|
||||||
@@ -1253,6 +1280,7 @@ mod tests {
|
|||||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Edit, Violation};
|
use crate::{Edit, Violation};
|
||||||
use crate::{Locator, generate_noqa_edits};
|
use crate::{Locator, generate_noqa_edits};
|
||||||
|
|
||||||
@@ -2848,6 +2876,7 @@ mod tests {
|
|||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 0);
|
assert_eq!(count, 0);
|
||||||
assert_eq!(output, format!("{contents}"));
|
assert_eq!(output, format!("{contents}"));
|
||||||
@@ -2872,6 +2901,7 @@ mod tests {
|
|||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 1);
|
assert_eq!(count, 1);
|
||||||
assert_eq!(output, "x = 1 # noqa: F841\n");
|
assert_eq!(output, "x = 1 # noqa: F841\n");
|
||||||
@@ -2903,6 +2933,7 @@ mod tests {
|
|||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 1);
|
assert_eq!(count, 1);
|
||||||
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
||||||
@@ -2934,6 +2965,7 @@ mod tests {
|
|||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 0);
|
assert_eq!(count, 0);
|
||||||
assert_eq!(output, "x = 1 # noqa");
|
assert_eq!(output, "x = 1 # noqa");
|
||||||
@@ -2956,6 +2988,7 @@ print(
|
|||||||
let messages = [PrintfStringFormatting
|
let messages = [PrintfStringFormatting
|
||||||
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
||||||
let comment_ranges = CommentRanges::default();
|
let comment_ranges = CommentRanges::default();
|
||||||
|
let suppressions = Suppressions::default();
|
||||||
let edits = generate_noqa_edits(
|
let edits = generate_noqa_edits(
|
||||||
path,
|
path,
|
||||||
&messages,
|
&messages,
|
||||||
@@ -2964,6 +2997,7 @@ print(
|
|||||||
&[],
|
&[],
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits,
|
edits,
|
||||||
@@ -2987,6 +3021,7 @@ bar =
|
|||||||
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
||||||
let noqa_line_for = NoqaMapping::default();
|
let noqa_line_for = NoqaMapping::default();
|
||||||
let comment_ranges = CommentRanges::default();
|
let comment_ranges = CommentRanges::default();
|
||||||
|
let suppressions = Suppressions::default();
|
||||||
let edits = generate_noqa_edits(
|
let edits = generate_noqa_edits(
|
||||||
path,
|
path,
|
||||||
&messages,
|
&messages,
|
||||||
@@ -2995,6 +3030,7 @@ bar =
|
|||||||
&[],
|
&[],
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits,
|
edits,
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ use crate::settings::LinterSettings;
|
|||||||
|
|
||||||
// Rule-specific behavior
|
// Rule-specific behavior
|
||||||
|
|
||||||
|
// https://github.com/astral-sh/ruff/pull/21382
|
||||||
|
pub(crate) const fn is_custom_exception_checking_enabled(settings: &LinterSettings) -> bool {
|
||||||
|
settings.preview.is_enabled()
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/astral-sh/ruff/pull/15541
|
// https://github.com/astral-sh/ruff/pull/15541
|
||||||
pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool {
|
pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool {
|
||||||
settings.preview.is_enabled()
|
settings.preview.is_enabled()
|
||||||
@@ -279,3 +284,15 @@ pub(crate) const fn is_extended_snmp_api_path_detection_enabled(settings: &Linte
|
|||||||
pub(crate) const fn is_enumerate_for_loop_int_index_enabled(settings: &LinterSettings) -> bool {
|
pub(crate) const fn is_enumerate_for_loop_int_index_enabled(settings: &LinterSettings) -> bool {
|
||||||
settings.preview.is_enabled()
|
settings.preview.is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/astral-sh/ruff/pull/21469
|
||||||
|
pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
|
||||||
|
settings: &LinterSettings,
|
||||||
|
) -> bool {
|
||||||
|
settings.preview.is_enabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/astral-sh/ruff/pull/21623
|
||||||
|
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
|
||||||
|
settings.preview.is_enabled()
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ static ALLOWLIST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
|||||||
# Case-sensitive
|
# Case-sensitive
|
||||||
pyright
|
pyright
|
||||||
| pyrefly
|
| pyrefly
|
||||||
|
| ruff\s*:\s*(disable|enable)
|
||||||
| mypy:
|
| mypy:
|
||||||
| type:\s*ignore
|
| type:\s*ignore
|
||||||
| SPDX-License-Identifier:
|
| SPDX-License-Identifier:
|
||||||
@@ -148,6 +149,8 @@ mod tests {
|
|||||||
assert!(!comment_contains_code("# 123", &[]));
|
assert!(!comment_contains_code("# 123", &[]));
|
||||||
assert!(!comment_contains_code("# 123.1", &[]));
|
assert!(!comment_contains_code("# 123.1", &[]));
|
||||||
assert!(!comment_contains_code("# 1, 2, 3", &[]));
|
assert!(!comment_contains_code("# 1, 2, 3", &[]));
|
||||||
|
assert!(!comment_contains_code("# ruff: disable[E501]", &[]));
|
||||||
|
assert!(!comment_contains_code("#ruff:enable[E501, F84]", &[]));
|
||||||
assert!(!comment_contains_code(
|
assert!(!comment_contains_code(
|
||||||
"# pylint: disable=redefined-outer-name",
|
"# pylint: disable=redefined-outer-name",
|
||||||
&[]
|
&[]
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def:
|
|||||||
response_model_arg,
|
response_model_arg,
|
||||||
&call.arguments,
|
&call.arguments,
|
||||||
Parentheses::Preserve,
|
Parentheses::Preserve,
|
||||||
checker.locator().contents(),
|
checker.source(),
|
||||||
checker.comment_ranges(),
|
checker.tokens(),
|
||||||
)
|
)
|
||||||
.map(Fix::unsafe_edit)
|
.map(Fix::unsafe_edit)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
||||||
fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
pub(crate) fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
|
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ mod async_zero_sleep;
|
|||||||
mod blocking_http_call;
|
mod blocking_http_call;
|
||||||
mod blocking_http_call_httpx;
|
mod blocking_http_call_httpx;
|
||||||
mod blocking_input;
|
mod blocking_input;
|
||||||
mod blocking_open_call;
|
pub(crate) mod blocking_open_call;
|
||||||
mod blocking_path_methods;
|
mod blocking_path_methods;
|
||||||
mod blocking_process_invocation;
|
mod blocking_process_invocation;
|
||||||
mod blocking_sleep;
|
mod blocking_sleep;
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ mod tests {
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
use crate::assert_diagnostics;
|
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
use crate::settings::types::PreviewMode;
|
use crate::settings::types::PreviewMode;
|
||||||
use crate::test::test_path;
|
use crate::test::test_path;
|
||||||
|
use crate::{assert_diagnostics, assert_diagnostics_diff};
|
||||||
|
|
||||||
#[test_case(Rule::Assert, Path::new("S101.py"))]
|
#[test_case(Rule::Assert, Path::new("S101.py"))]
|
||||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
|
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
|
||||||
@@ -112,14 +112,19 @@ mod tests {
|
|||||||
rule_code.noqa_code(),
|
rule_code.noqa_code(),
|
||||||
path.to_string_lossy()
|
path.to_string_lossy()
|
||||||
);
|
);
|
||||||
let diagnostics = test_path(
|
|
||||||
|
assert_diagnostics_diff!(
|
||||||
|
snapshot,
|
||||||
Path::new("flake8_bandit").join(path).as_path(),
|
Path::new("flake8_bandit").join(path).as_path(),
|
||||||
|
&LinterSettings {
|
||||||
|
preview: PreviewMode::Disabled,
|
||||||
|
..LinterSettings::for_rule(rule_code)
|
||||||
|
},
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
preview: PreviewMode::Enabled,
|
preview: PreviewMode::Enabled,
|
||||||
..LinterSettings::for_rule(rule_code)
|
..LinterSettings::for_rule(rule_code)
|
||||||
},
|
}
|
||||||
)?;
|
);
|
||||||
assert_diagnostics!(snapshot, diagnostics);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,16 @@
|
|||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator};
|
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator};
|
||||||
|
use ruff_python_semantic::SemanticModel;
|
||||||
|
use ruff_python_semantic::analyze::typing::find_binding_value;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::Violation;
|
use crate::Violation;
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::preview::is_suspicious_function_reference_enabled;
|
use crate::preview::{
|
||||||
|
is_s310_resolve_string_literal_bindings_enabled, is_suspicious_function_reference_enabled,
|
||||||
|
};
|
||||||
|
use crate::settings::LinterSettings;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for calls to `pickle` functions or modules that wrap them.
|
/// Checks for calls to `pickle` functions or modules that wrap them.
|
||||||
@@ -1016,6 +1021,25 @@ fn suspicious_function(
|
|||||||
|| has_prefix(chars.skip_while(|c| c.is_whitespace()), "https://")
|
|| has_prefix(chars.skip_while(|c| c.is_whitespace()), "https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves `expr` to its binding and checks if the resolved expression starts with an HTTP or HTTPS prefix.
|
||||||
|
fn expression_starts_with_http_prefix(
|
||||||
|
expr: &Expr,
|
||||||
|
semantic: &SemanticModel,
|
||||||
|
settings: &LinterSettings,
|
||||||
|
) -> bool {
|
||||||
|
let resolved_expression = if is_s310_resolve_string_literal_bindings_enabled(settings)
|
||||||
|
&& let Some(name_expr) = expr.as_name_expr()
|
||||||
|
&& let Some(binding_id) = semantic.only_binding(name_expr)
|
||||||
|
&& let Some(value) = find_binding_value(semantic.binding(binding_id), semantic)
|
||||||
|
{
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
expr
|
||||||
|
};
|
||||||
|
|
||||||
|
leading_chars(resolved_expression).is_some_and(has_http_prefix)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the leading characters for an expression, if it's a string literal, f-string, or
|
/// Return the leading characters for an expression, if it's a string literal, f-string, or
|
||||||
/// string concatenation.
|
/// string concatenation.
|
||||||
fn leading_chars(expr: &Expr) -> Option<impl Iterator<Item = char> + Clone + '_> {
|
fn leading_chars(expr: &Expr) -> Option<impl Iterator<Item = char> + Clone + '_> {
|
||||||
@@ -1139,17 +1163,19 @@ fn suspicious_function(
|
|||||||
// URLOpen (`Request`)
|
// URLOpen (`Request`)
|
||||||
["urllib", "request", "Request"] | ["six", "moves", "urllib", "request", "Request"] => {
|
["urllib", "request", "Request"] | ["six", "moves", "urllib", "request", "Request"] => {
|
||||||
if let Some(arguments) = arguments {
|
if let Some(arguments) = arguments {
|
||||||
// If the `url` argument is a string literal or an f-string, allow `http` and `https` schemes.
|
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
|
||||||
if arguments.args.iter().all(|arg| !arg.is_starred_expr())
|
if arguments.args.iter().all(|arg| !arg.is_starred_expr())
|
||||||
&& arguments
|
&& arguments
|
||||||
.keywords
|
.keywords
|
||||||
.iter()
|
.iter()
|
||||||
.all(|keyword| keyword.arg.is_some())
|
.all(|keyword| keyword.arg.is_some())
|
||||||
{
|
{
|
||||||
if arguments
|
if let Some(url_expr) = arguments.find_argument_value("url", 0)
|
||||||
.find_argument_value("url", 0)
|
&& expression_starts_with_http_prefix(
|
||||||
.and_then(leading_chars)
|
url_expr,
|
||||||
.is_some_and(has_http_prefix)
|
checker.semantic(),
|
||||||
|
checker.settings(),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1186,19 +1212,25 @@ fn suspicious_function(
|
|||||||
name.segments() == ["urllib", "request", "Request"]
|
name.segments() == ["urllib", "request", "Request"]
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
if arguments
|
if let Some(url_expr) = arguments.find_argument_value("url", 0)
|
||||||
.find_argument_value("url", 0)
|
&& expression_starts_with_http_prefix(
|
||||||
.and_then(leading_chars)
|
url_expr,
|
||||||
.is_some_and(has_http_prefix)
|
checker.semantic(),
|
||||||
|
checker.settings(),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the `url` argument is a string literal, allow `http` and `https` schemes.
|
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
|
||||||
Some(expr) => {
|
Some(expr) => {
|
||||||
if leading_chars(expr).is_some_and(has_http_prefix) {
|
if expression_starts_with_http_prefix(
|
||||||
|
expr,
|
||||||
|
checker.semantic(),
|
||||||
|
checker.settings(),
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::{checkers::ast::Checker, settings::LinterSettings};
|
|||||||
/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup].
|
/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup].
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// [`markupsafe.Markup`] does not perform any escaping, so passing dynamic
|
/// [`markupsafe.Markup`][markupsafe-markup] does not perform any escaping, so passing dynamic
|
||||||
/// content, like f-strings, variables or interpolated strings will potentially
|
/// content, like f-strings, variables or interpolated strings will potentially
|
||||||
/// lead to XSS vulnerabilities.
|
/// lead to XSS vulnerabilities.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ pub(crate) fn unsafe_yaml_load(checker: &Checker, call: &ast::ExprCall) {
|
|||||||
qualified_name.segments(),
|
qualified_name.segments(),
|
||||||
["yaml", "SafeLoader" | "CSafeLoader"]
|
["yaml", "SafeLoader" | "CSafeLoader"]
|
||||||
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
||||||
|
| ["yaml", "cyaml", "CSafeLoader"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -254,3 +254,84 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
|||||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:51:1
|
||||||
|
|
|
||||||
|
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||||
|
50 | path = "https://example.com/data.csv"
|
||||||
|
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
52 | url = "https://example.com/api"
|
||||||
|
53 | urllib.request.Request(url)
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:53:1
|
||||||
|
|
|
||||||
|
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||||
|
52 | url = "https://example.com/api"
|
||||||
|
53 | urllib.request.Request(url)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
54 |
|
||||||
|
55 | # Test resolved f-strings and concatenated string literals
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:57:1
|
||||||
|
|
|
||||||
|
55 | # Test resolved f-strings and concatenated string literals
|
||||||
|
56 | fstring_url = f"https://example.com/data.csv"
|
||||||
|
57 | urllib.request.urlopen(fstring_url)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
58 | urllib.request.Request(fstring_url)
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:58:1
|
||||||
|
|
|
||||||
|
56 | fstring_url = f"https://example.com/data.csv"
|
||||||
|
57 | urllib.request.urlopen(fstring_url)
|
||||||
|
58 | urllib.request.Request(fstring_url)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
59 |
|
||||||
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:61:1
|
||||||
|
|
|
||||||
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
|
61 | urllib.request.urlopen(concatenated_url)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
62 | urllib.request.Request(concatenated_url)
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:62:1
|
||||||
|
|
|
||||||
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
|
61 | urllib.request.urlopen(concatenated_url)
|
||||||
|
62 | urllib.request.Request(concatenated_url)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
63 |
|
||||||
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:65:1
|
||||||
|
|
|
||||||
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
|
65 | urllib.request.urlopen(nested_concatenated)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
66 | urllib.request.Request(nested_concatenated)
|
||||||
|
|
|
||||||
|
|
||||||
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
|
--> S310.py:66:1
|
||||||
|
|
|
||||||
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
|
65 | urllib.request.urlopen(nested_concatenated)
|
||||||
|
66 | urllib.request.Request(nested_concatenated)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
--- Linter settings ---
|
||||||
--> S301.py:3:1
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
1 | import pickle
|
|
||||||
2 |
|
|
||||||
3 | pickle.loads()
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Summary ---
|
||||||
|
Removed: 0
|
||||||
|
Added: 2
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
||||||
--> S301.py:7:5
|
--> S301.py:7:5
|
||||||
|
|
|
|
||||||
@@ -19,6 +19,7 @@ S301 `pickle` and modules that wrap it can be unsafe when used to deserialize un
|
|||||||
8 | foo = pickle.load
|
8 | foo = pickle.load
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
||||||
--> S301.py:8:7
|
--> S301.py:8:7
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
--- Linter settings ---
|
||||||
--> S307.py:3:7
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
1 | import os
|
|
||||||
2 |
|
|
||||||
3 | print(eval("1+1")) # S307
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
4 | print(eval("os.getcwd()")) # S307
|
|
||||||
|
|
|
||||||
|
|
||||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
--- Summary ---
|
||||||
--> S307.py:4:7
|
Removed: 0
|
||||||
|
|
Added: 2
|
||||||
3 | print(eval("1+1")) # S307
|
|
||||||
4 | print(eval("os.getcwd()")) # S307
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||||
--> S307.py:16:5
|
--> S307.py:16:5
|
||||||
|
|
|
|
||||||
@@ -28,6 +19,7 @@ S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
|||||||
17 | foo = eval
|
17 | foo = eval
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||||
--> S307.py:17:7
|
--> S307.py:17:7
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,60 +1,37 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
--- Linter settings ---
|
||||||
--> S308.py:6:5
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
4 | def bad_func():
|
|
||||||
5 | inject = "harmful_input"
|
|
||||||
6 | mark_safe(inject)
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
7 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
8 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
--- Summary ---
|
||||||
--> S308.py:7:5
|
Removed: 2
|
||||||
|
|
Added: 4
|
||||||
5 | inject = "harmful_input"
|
|
||||||
6 | mark_safe(inject)
|
|
||||||
7 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
8 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
9 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Removed ---
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:8:5
|
--> S308.py:16:1
|
||||||
|
|
|
|
||||||
6 | mark_safe(inject)
|
16 | @mark_safe
|
||||||
7 | mark_safe("I will add" + inject + "to my string")
|
| ^^^^^^^^^^
|
||||||
8 | mark_safe("I will add %s to my string" % inject)
|
17 | def some_func():
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
18 | return '<script>alert("evil!")</script>'
|
||||||
9 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
10 | mark_safe(f"I will add {inject} to my string")
|
|
||||||
|
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:9:5
|
|
||||||
|
|
|
||||||
7 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
8 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
9 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
10 | mark_safe(f"I will add {inject} to my string")
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:10:5
|
--> S308.py:36:1
|
||||||
|
|
|
|
||||||
8 | mark_safe("I will add %s to my string" % inject)
|
36 | @mark_safe
|
||||||
9 | mark_safe("I will add {} to my string".format(inject))
|
| ^^^^^^^^^^
|
||||||
10 | mark_safe(f"I will add {inject} to my string")
|
37 | def some_func():
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
38 | return '<script>alert("evil!")</script>'
|
||||||
11 |
|
|
||||||
12 | def good_func():
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:16:2
|
--> S308.py:16:2
|
||||||
|
|
|
|
||||||
@@ -64,59 +41,6 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|||||||
18 | return '<script>alert("evil!")</script>'
|
18 | return '<script>alert("evil!")</script>'
|
||||||
|
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:26:5
|
|
||||||
|
|
|
||||||
24 | def bad_func():
|
|
||||||
25 | inject = "harmful_input"
|
|
||||||
26 | mark_safe(inject)
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
27 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
28 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:27:5
|
|
||||||
|
|
|
||||||
25 | inject = "harmful_input"
|
|
||||||
26 | mark_safe(inject)
|
|
||||||
27 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
28 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
29 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:28:5
|
|
||||||
|
|
|
||||||
26 | mark_safe(inject)
|
|
||||||
27 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
28 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
29 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
30 | mark_safe(f"I will add {inject} to my string")
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:29:5
|
|
||||||
|
|
|
||||||
27 | mark_safe("I will add" + inject + "to my string")
|
|
||||||
28 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
29 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
30 | mark_safe(f"I will add {inject} to my string")
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|
||||||
--> S308.py:30:5
|
|
||||||
|
|
|
||||||
28 | mark_safe("I will add %s to my string" % inject)
|
|
||||||
29 | mark_safe("I will add {} to my string".format(inject))
|
|
||||||
30 | mark_safe(f"I will add {inject} to my string")
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
31 |
|
|
||||||
32 | def good_func():
|
|
||||||
|
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:36:2
|
--> S308.py:36:2
|
||||||
@@ -127,6 +51,7 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|||||||
38 | return '<script>alert("evil!")</script>'
|
38 | return '<script>alert("evil!")</script>'
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:42:5
|
--> S308.py:42:5
|
||||||
|
|
|
|
||||||
@@ -136,6 +61,7 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
|||||||
43 | foo = mark_safe
|
43 | foo = mark_safe
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||||
--> S308.py:43:7
|
--> S308.py:43:7
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,260 +1,106 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
--- Linter settings ---
|
||||||
--> S310.py:6:1
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
4 | urllib.request.urlopen(url=f'http://www.google.com')
|
|
||||||
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
|
|
||||||
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
|
|
||||||
8 | urllib.request.urlopen('http://www.google.com')
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
--- Summary ---
|
||||||
--> S310.py:7:1
|
Removed: 8
|
||||||
|
|
Added: 2
|
||||||
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
|
|
||||||
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
|
|
||||||
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
8 | urllib.request.urlopen('http://www.google.com')
|
|
||||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Removed ---
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:10:1
|
--> S310.py:51:1
|
||||||
|
|
|
|
||||||
8 | urllib.request.urlopen('http://www.google.com')
|
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
50 | path = "https://example.com/data.csv"
|
||||||
10 | urllib.request.urlopen('file:///foo/bar/baz')
|
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
11 | urllib.request.urlopen(url)
|
52 | url = "https://example.com/api"
|
||||||
|
53 | urllib.request.Request(url)
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:11:1
|
--> S310.py:53:1
|
||||||
|
|
|
|
||||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||||
10 | urllib.request.urlopen('file:///foo/bar/baz')
|
52 | url = "https://example.com/api"
|
||||||
11 | urllib.request.urlopen(url)
|
53 | urllib.request.Request(url)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
12 |
|
54 |
|
||||||
13 | urllib.request.Request(url='http://www.google.com')
|
55 | # Test resolved f-strings and concatenated string literals
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:16:1
|
--> S310.py:57:1
|
||||||
|
|
|
|
||||||
14 | urllib.request.Request(url=f'http://www.google.com')
|
55 | # Test resolved f-strings and concatenated string literals
|
||||||
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
|
56 | fstring_url = f"https://example.com/data.csv"
|
||||||
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
|
57 | urllib.request.urlopen(fstring_url)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
|
58 | urllib.request.Request(fstring_url)
|
||||||
18 | urllib.request.Request('http://www.google.com')
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:17:1
|
--> S310.py:58:1
|
||||||
|
|
|
|
||||||
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
|
56 | fstring_url = f"https://example.com/data.csv"
|
||||||
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
|
57 | urllib.request.urlopen(fstring_url)
|
||||||
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
|
58 | urllib.request.Request(fstring_url)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
18 | urllib.request.Request('http://www.google.com')
|
59 |
|
||||||
19 | urllib.request.Request(f'http://www.google.com')
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:20:1
|
--> S310.py:61:1
|
||||||
|
|
|
|
||||||
18 | urllib.request.Request('http://www.google.com')
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
19 | urllib.request.Request(f'http://www.google.com')
|
61 | urllib.request.urlopen(concatenated_url)
|
||||||
20 | urllib.request.Request('file:///foo/bar/baz')
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
62 | urllib.request.Request(concatenated_url)
|
||||||
21 | urllib.request.Request(url)
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:21:1
|
--> S310.py:62:1
|
||||||
|
|
|
|
||||||
19 | urllib.request.Request(f'http://www.google.com')
|
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||||
20 | urllib.request.Request('file:///foo/bar/baz')
|
61 | urllib.request.urlopen(concatenated_url)
|
||||||
21 | urllib.request.Request(url)
|
62 | urllib.request.Request(concatenated_url)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
22 |
|
63 |
|
||||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:23:1
|
--> S310.py:65:1
|
||||||
|
|
|
|
||||||
21 | urllib.request.Request(url)
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
22 |
|
65 | urllib.request.urlopen(nested_concatenated)
|
||||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
66 | urllib.request.Request(nested_concatenated)
|
||||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
|
||||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:24:1
|
--> S310.py:66:1
|
||||||
|
|
|
|
||||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
65 | urllib.request.urlopen(nested_concatenated)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
66 | urllib.request.Request(nested_concatenated)
|
||||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
|
||||||
|
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:25:1
|
|
||||||
|
|
|
||||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
|
||||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
|
||||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
|
||||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:26:1
|
|
||||||
|
|
|
||||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
|
||||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
|
||||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
|
||||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:27:1
|
|
||||||
|
|
|
||||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
|
||||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
|
||||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
|
||||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:28:1
|
|
||||||
|
|
|
||||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
|
||||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
|
||||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
|
||||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:29:1
|
|
||||||
|
|
|
||||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
|
||||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
|
||||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
|
||||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:30:1
|
|
||||||
|
|
|
||||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
|
||||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
|
||||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
|
||||||
32 | urllib.request.URLopener().open(url)
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:31:1
|
|
||||||
|
|
|
||||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
|
||||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
|
||||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
32 | urllib.request.URLopener().open(url)
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:32:1
|
|
||||||
|
|
|
||||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
|
||||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
|
||||||
32 | urllib.request.URLopener().open(url)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
33 |
|
|
||||||
34 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:37:1
|
|
||||||
|
|
|
||||||
35 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'))
|
|
||||||
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
|
|
||||||
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
|
|
||||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:38:1
|
|
||||||
|
|
|
||||||
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
|
|
||||||
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
|
|
||||||
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
|
||||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:41:1
|
|
||||||
|
|
|
||||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
|
||||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
|
||||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:41:24
|
|
||||||
|
|
|
||||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
|
||||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
|
||||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:42:1
|
|
||||||
|
|
|
||||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
|
||||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
|
||||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
|
||||||
--> S310.py:42:24
|
|
||||||
|
|
|
||||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
|
||||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
|
||||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:46:5
|
--> S310.py:46:5
|
||||||
|
|
|
|
||||||
@@ -264,6 +110,7 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
|||||||
47 | foo = urllib.request.urlopen
|
47 | foo = urllib.request.urlopen
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||||
--> S310.py:47:7
|
--> S310.py:47:7
|
||||||
|
|
|
|
||||||
@@ -271,4 +118,6 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
|||||||
46 | map(urllib.request.urlopen, [])
|
46 | map(urllib.request.urlopen, [])
|
||||||
47 | foo = urllib.request.urlopen
|
47 | foo = urllib.request.urlopen
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
48 |
|
||||||
|
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,103 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
--- Linter settings ---
|
||||||
--> S311.py:10:1
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
9 | # Errors
|
|
||||||
10 | random.Random()
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
11 | random.random()
|
|
||||||
12 | random.randrange()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
--- Summary ---
|
||||||
--> S311.py:11:1
|
Removed: 0
|
||||||
|
|
Added: 2
|
||||||
9 | # Errors
|
|
||||||
10 | random.Random()
|
|
||||||
11 | random.random()
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
12 | random.randrange()
|
|
||||||
13 | random.randint()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:12:1
|
|
||||||
|
|
|
||||||
10 | random.Random()
|
|
||||||
11 | random.random()
|
|
||||||
12 | random.randrange()
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
13 | random.randint()
|
|
||||||
14 | random.choice()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:13:1
|
|
||||||
|
|
|
||||||
11 | random.random()
|
|
||||||
12 | random.randrange()
|
|
||||||
13 | random.randint()
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
14 | random.choice()
|
|
||||||
15 | random.choices()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:14:1
|
|
||||||
|
|
|
||||||
12 | random.randrange()
|
|
||||||
13 | random.randint()
|
|
||||||
14 | random.choice()
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
15 | random.choices()
|
|
||||||
16 | random.uniform()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:15:1
|
|
||||||
|
|
|
||||||
13 | random.randint()
|
|
||||||
14 | random.choice()
|
|
||||||
15 | random.choices()
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
16 | random.uniform()
|
|
||||||
17 | random.triangular()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:16:1
|
|
||||||
|
|
|
||||||
14 | random.choice()
|
|
||||||
15 | random.choices()
|
|
||||||
16 | random.uniform()
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
17 | random.triangular()
|
|
||||||
18 | random.randbytes()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:17:1
|
|
||||||
|
|
|
||||||
15 | random.choices()
|
|
||||||
16 | random.uniform()
|
|
||||||
17 | random.triangular()
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
18 | random.randbytes()
|
|
||||||
|
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
--> S311.py:18:1
|
|
||||||
|
|
|
||||||
16 | random.uniform()
|
|
||||||
17 | random.triangular()
|
|
||||||
18 | random.randbytes()
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
19 |
|
|
||||||
20 | # Unrelated
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||||
--> S311.py:26:5
|
--> S311.py:26:5
|
||||||
|
|
|
|
||||||
@@ -107,6 +19,7 @@ S311 Standard pseudo-random generators are not suitable for cryptographic purpos
|
|||||||
27 | foo = random.randrange
|
27 | foo = random.randrange
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||||
--> S311.py:27:7
|
--> S311.py:27:7
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
--- Linter settings ---
|
||||||
--> S312.py:3:1
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
1 | from telnetlib import Telnet
|
|
||||||
2 |
|
|
||||||
3 | Telnet("localhost", 23)
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Summary ---
|
||||||
|
Removed: 0
|
||||||
|
Added: 3
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||||
--> S312.py:7:5
|
--> S312.py:7:5
|
||||||
|
|
|
|
||||||
@@ -19,6 +19,7 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
|||||||
8 | foo = Telnet
|
8 | foo = Telnet
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||||
--> S312.py:8:7
|
--> S312.py:8:7
|
||||||
|
|
|
|
||||||
@@ -30,6 +31,7 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
|||||||
10 | import telnetlib
|
10 | import telnetlib
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||||
--> S312.py:11:5
|
--> S312.py:11:5
|
||||||
|
|
|
|
||||||
@@ -39,13 +41,3 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
|||||||
12 |
|
12 |
|
||||||
13 | from typing import Annotated
|
13 | from typing import Annotated
|
||||||
|
|
|
|
||||||
|
|
||||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
|
||||||
--> S312.py:14:24
|
|
||||||
|
|
|
||||||
13 | from typing import Annotated
|
|
||||||
14 | foo: Annotated[Telnet, telnetlib.Telnet()]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
15 |
|
|
||||||
16 | def _() -> Telnet: ...
|
|
||||||
|
|
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
--- Linter settings ---
|
||||||
--> S508.py:3:25
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
1 | from pysnmp.hlapi import CommunityData
|
|
||||||
2 |
|
|
||||||
3 | CommunityData("public", mpModel=0) # S508
|
|
||||||
| ^^^^^^^^^
|
|
||||||
4 | CommunityData("public", mpModel=1) # S508
|
|
||||||
|
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
--- Summary ---
|
||||||
--> S508.py:4:25
|
Removed: 0
|
||||||
|
|
Added: 8
|
||||||
3 | CommunityData("public", mpModel=0) # S508
|
|
||||||
4 | CommunityData("public", mpModel=1) # S508
|
|
||||||
| ^^^^^^^^^
|
|
||||||
5 |
|
|
||||||
6 | CommunityData("public", mpModel=2) # OK
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:18:46
|
--> S508.py:18:46
|
||||||
|
|
|
|
||||||
@@ -32,6 +21,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508
|
20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:19:58
|
--> S508.py:19:58
|
||||||
|
|
|
|
||||||
@@ -42,6 +32,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508
|
21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:20:53
|
--> S508.py:20:53
|
||||||
|
|
|
|
||||||
@@ -53,6 +44,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508
|
22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:21:45
|
--> S508.py:21:45
|
||||||
|
|
|
|
||||||
@@ -64,6 +56,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508
|
23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:22:58
|
--> S508.py:22:58
|
||||||
|
|
|
|
||||||
@@ -75,6 +68,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508
|
24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:23:53
|
--> S508.py:23:53
|
||||||
|
|
|
|
||||||
@@ -86,6 +80,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:24:45
|
--> S508.py:24:45
|
||||||
|
|
|
|
||||||
@@ -96,6 +91,7 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
|||||||
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||||
--> S508.py:25:43
|
--> S508.py:25:43
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||||
---
|
---
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
--- Linter settings ---
|
||||||
--> S509.py:4:12
|
-linter.preview = disabled
|
||||||
|
|
+linter.preview = enabled
|
||||||
4 | insecure = UsmUserData("securityName") # S509
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
|
|
||||||
|
|
|
||||||
|
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
--- Summary ---
|
||||||
--> S509.py:5:16
|
Removed: 0
|
||||||
|
|
Added: 4
|
||||||
4 | insecure = UsmUserData("securityName") # S509
|
|
||||||
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
6 |
|
|
||||||
7 | less_insecure = UsmUserData("securityName", "authName", "privName") # OK
|
|
||||||
|
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||||
--> S509.py:15:1
|
--> S509.py:15:1
|
||||||
|
|
|
|
||||||
@@ -30,6 +21,7 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
|||||||
17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509
|
17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||||
--> S509.py:16:1
|
--> S509.py:16:1
|
||||||
|
|
|
|
||||||
@@ -40,6 +32,7 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
|||||||
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||||
--> S509.py:17:1
|
--> S509.py:17:1
|
||||||
|
|
|
|
||||||
@@ -50,6 +43,7 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
|||||||
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||||
--> S509.py:18:1
|
--> S509.py:18:1
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
|||||||
/// keyword-only argument, to force callers to be explicit when providing
|
/// keyword-only argument, to force callers to be explicit when providing
|
||||||
/// the argument.
|
/// the argument.
|
||||||
///
|
///
|
||||||
|
/// This rule exempts methods decorated with [`@typing.override`][override],
|
||||||
|
/// since changing the signature of a subclass method that overrides a
|
||||||
|
/// superclass method may cause type checkers to complain about a violation of
|
||||||
|
/// the Liskov Substitution Principle.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// from math import ceil, floor
|
/// from math import ceil, floor
|
||||||
@@ -89,6 +94,8 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
|||||||
/// ## References
|
/// ## References
|
||||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||||
|
///
|
||||||
|
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
#[violation_metadata(stable_since = "v0.0.127")]
|
#[violation_metadata(stable_since = "v0.0.127")]
|
||||||
pub(crate) struct BooleanDefaultValuePositionalArgument;
|
pub(crate) struct BooleanDefaultValuePositionalArgument;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
|||||||
/// the argument.
|
/// the argument.
|
||||||
///
|
///
|
||||||
/// Dunder methods that define operators are exempt from this rule, as are
|
/// Dunder methods that define operators are exempt from this rule, as are
|
||||||
/// setters and `@override` definitions.
|
/// setters and [`@override`][override] definitions.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@@ -93,6 +93,8 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
|||||||
/// ## References
|
/// ## References
|
||||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||||
|
///
|
||||||
|
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
#[violation_metadata(stable_since = "v0.0.127")]
|
#[violation_metadata(stable_since = "v0.0.127")]
|
||||||
pub(crate) struct BooleanTypeHintPositionalArgument;
|
pub(crate) struct BooleanTypeHintPositionalArgument;
|
||||||
|
|||||||
@@ -74,12 +74,7 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
|||||||
checker
|
checker
|
||||||
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
||||||
.set_fix(Fix::applicable_edit(
|
.set_fix(Fix::applicable_edit(
|
||||||
add_argument(
|
add_argument("strict=False", &call.arguments, checker.tokens()),
|
||||||
"strict=False",
|
|
||||||
&call.arguments,
|
|
||||||
checker.comment_ranges(),
|
|
||||||
checker.locator().contents(),
|
|
||||||
),
|
|
||||||
Applicability::Unsafe,
|
Applicability::Unsafe,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user