Compare commits
100 Commits
0.11.9
...
david/stab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e83ce9e5d | ||
|
|
d998bea5de | ||
|
|
3a76557be2 | ||
|
|
2a217e80ca | ||
|
|
030a16cb5f | ||
|
|
0590b38214 | ||
|
|
8104b1e83b | ||
|
|
cf70c7863c | ||
|
|
faf54c0181 | ||
|
|
451c5db7a3 | ||
|
|
bd5b7f415f | ||
|
|
0230cbac2c | ||
|
|
2e94d37275 | ||
|
|
1e4377c9c6 | ||
|
|
1b4f7de840 | ||
|
|
9b52ae8991 | ||
|
|
97d7b46936 | ||
|
|
1eab59e681 | ||
|
|
e7f97a3e4b | ||
|
|
d17557f0ae | ||
|
|
8cbd433a31 | ||
|
|
65e48cb439 | ||
|
|
301d9985d8 | ||
|
|
cfbb914100 | ||
|
|
fe653de3dd | ||
|
|
a9f7521944 | ||
|
|
f8890b70c3 | ||
|
|
142c1bc760 | ||
|
|
c0f22928bd | ||
|
|
5bf5f3682a | ||
|
|
5913997c72 | ||
|
|
00f672a83b | ||
|
|
68b0386007 | ||
|
|
0ae07cdd1f | ||
|
|
0fb94c052e | ||
|
|
55df9271ba | ||
|
|
f301931159 | ||
|
|
7e9b0df18a | ||
|
|
41fa082414 | ||
|
|
c7b6108cb8 | ||
|
|
a97e72fb5e | ||
|
|
0d6fafd0f9 | ||
|
|
2eb2d5359b | ||
|
|
f549dfe39d | ||
|
|
6b64630635 | ||
|
|
d545b5bfd2 | ||
|
|
b2d9f59937 | ||
|
|
d7ef01401c | ||
|
|
c9031ce59f | ||
|
|
138ab91def | ||
|
|
550b8be552 | ||
|
|
bdccb37b4a | ||
|
|
3ccc0edfe4 | ||
|
|
6b3ff6f5b8 | ||
|
|
6f8f7506b4 | ||
|
|
797eb70904 | ||
|
|
be6ec613db | ||
|
|
fcd858e0c8 | ||
|
|
d944a1397e | ||
|
|
d3f3d92df3 | ||
|
|
38c00dfad5 | ||
|
|
d6280c5aea | ||
|
|
a34240a3f0 | ||
|
|
b86c7bbf7c | ||
|
|
d7c54ba8c4 | ||
|
|
c38d6e8045 | ||
|
|
2bfd7b1816 | ||
|
|
c1cfb43bf0 | ||
|
|
99555b775c | ||
|
|
b398b83631 | ||
|
|
bc7b30364d | ||
|
|
5792ed15da | ||
|
|
8845a13efb | ||
|
|
669855d2b5 | ||
|
|
ff7ebecf89 | ||
|
|
7e8ba2b68e | ||
|
|
0bb8cbdf07 | ||
|
|
2923c55698 | ||
|
|
316e406ca4 | ||
|
|
5ecd560c6f | ||
|
|
b765dc48e9 | ||
|
|
cd1d906ffa | ||
|
|
235b74a310 | ||
|
|
40fd52dde0 | ||
|
|
fd1eb3d801 | ||
|
|
882a1a702e | ||
|
|
b4a1ebdfe3 | ||
|
|
7a48477c67 | ||
|
|
346e82b572 | ||
|
|
5ea3a52c8a | ||
|
|
90272ad85a | ||
|
|
e9da1750a1 | ||
|
|
25e13debc0 | ||
|
|
249a852a6e | ||
|
|
861ef2504e | ||
|
|
b71ef8a26e | ||
|
|
50c780fc8b | ||
|
|
244ea27d5f | ||
|
|
2c4cbb6e29 | ||
|
|
d1bb10a66b |
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,8 +1,9 @@
|
||||
<!--
|
||||
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
|
||||
Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following:
|
||||
|
||||
- Does this pull request include a summary of the change? (See below.)
|
||||
- Does this pull request include a descriptive title?
|
||||
- Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull
|
||||
requests.)
|
||||
- Does this pull request include references to any relevant issues?
|
||||
-->
|
||||
|
||||
|
||||
12
.github/workflows/build-docker.yml
vendored
12
.github/workflows/build-docker.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
${{ env.TAG_PATTERNS }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -276,7 +276,7 @@ jobs:
|
||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -239,11 +239,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -297,11 +297,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -407,11 +407,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -437,7 +437,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -692,7 +692,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
- uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -908,7 +908,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
2
.github/workflows/mypy_primer.yaml
vendored
2
.github/workflows/mypy_primer.yaml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@4b15cf3b07db69db67bbfaebfffb2a8a28040933" \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@968b2b61c05f84462d6fcc78d2f5205bbb8b98c2" \
|
||||
mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker ty \
|
||||
|
||||
@@ -80,7 +80,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.8
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -98,7 +98,7 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.6.0
|
||||
rev: v1.7.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing
|
||||
> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
|
||||
Ruff welcomes contributions in the form of pull requests.
|
||||
|
||||
58
Cargo.lock
generated
58
Cargo.lock
generated
@@ -334,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.37"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -344,9 +344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.37"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -409,7 +409,7 @@ version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"terminfo",
|
||||
"thiserror 2.0.12",
|
||||
"which",
|
||||
@@ -681,11 +681,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.6"
|
||||
version = "3.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
|
||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -1057,9 +1057,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -1539,9 +1539,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd"
|
||||
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -1554,9 +1554,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300"
|
||||
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1880,6 +1880,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -2458,7 +2470,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3138,7 +3150,7 @@ version = "0.11.9"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_formatter",
|
||||
@@ -3219,6 +3231,12 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-stable-hash"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@@ -3625,7 +3643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -3984,6 +4002,7 @@ dependencies = [
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"filetime",
|
||||
"indicatif",
|
||||
"insta",
|
||||
"insta-cmd",
|
||||
"jiff",
|
||||
@@ -4140,6 +4159,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-stable-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -4167,7 +4187,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_db",
|
||||
@@ -4352,7 +4372,7 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"rand 0.9.1",
|
||||
"uuid-macro-internal",
|
||||
|
||||
@@ -125,6 +125,7 @@ rand = { version = "0.9.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
||||
schemars = { version = "0.8.16" }
|
||||
|
||||
@@ -255,7 +255,7 @@ indent-width = 4
|
||||
target-version = "py39"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ dist = true
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -15,6 +15,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "aix"),
|
||||
not(target_os = "android"),
|
||||
any(
|
||||
target_arch = "x86_64",
|
||||
target_arch = "aarch64",
|
||||
|
||||
@@ -301,6 +301,62 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[many_tuple_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a micro benchmark, but it is effectively identical to a code sample
|
||||
// observed in https://github.com/astral-sh/ty/issues/362
|
||||
setup_micro_case(
|
||||
r#"
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = ()
|
||||
if flag():
|
||||
t += (1,)
|
||||
if flag():
|
||||
t += (2,)
|
||||
if flag():
|
||||
t += (3,)
|
||||
if flag():
|
||||
t += (4,)
|
||||
if flag():
|
||||
t += (5,)
|
||||
if flag():
|
||||
t += (6,)
|
||||
if flag():
|
||||
t += (7,)
|
||||
if flag():
|
||||
t += (8,)
|
||||
if flag():
|
||||
t += (9,)
|
||||
if flag():
|
||||
t += (10,)
|
||||
if flag():
|
||||
t += (11,)
|
||||
|
||||
# Perform some kind of operation on the union type
|
||||
print(1 in t)
|
||||
"#,
|
||||
)
|
||||
},
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
|
||||
criterion_group!(micro, benchmark_many_string_assignments);
|
||||
criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments
|
||||
);
|
||||
criterion_main!(check_file, micro);
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::{fmt::Formatter, sync::Arc};
|
||||
|
||||
use render::{FileResolver, Input};
|
||||
use ruff_source_file::{SourceCode, SourceFile};
|
||||
use thiserror::Error;
|
||||
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -613,40 +612,19 @@ impl DiagnosticId {
|
||||
code.split_once(':').map(|(_, rest)| rest)
|
||||
}
|
||||
|
||||
/// Returns `true` if this `DiagnosticId` matches the given name.
|
||||
/// Returns a concise description of this diagnostic ID.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::DiagnosticId;
|
||||
///
|
||||
/// assert!(DiagnosticId::Io.matches("io"));
|
||||
/// assert!(DiagnosticId::lint("test").matches("lint:test"));
|
||||
/// assert!(!DiagnosticId::lint("test").matches("test"));
|
||||
/// ```
|
||||
pub fn matches(&self, expected_name: &str) -> bool {
|
||||
match self.as_str() {
|
||||
Ok(id) => id == expected_name,
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => expected_name
|
||||
.strip_prefix(category)
|
||||
.and_then(|prefix| prefix.strip_prefix(":"))
|
||||
.is_some_and(|rest| rest == name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> {
|
||||
Ok(match self {
|
||||
/// Note that this doesn't include the lint's category. It
|
||||
/// only includes the lint's name.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
DiagnosticId::Panic => "panic",
|
||||
DiagnosticId::Io => "io",
|
||||
DiagnosticId::InvalidSyntax => "invalid-syntax",
|
||||
DiagnosticId::Lint(name) => {
|
||||
return Err(DiagnosticAsStrError::Category {
|
||||
category: "lint",
|
||||
name: name.as_str(),
|
||||
})
|
||||
}
|
||||
DiagnosticId::Lint(name) => name.as_str(),
|
||||
DiagnosticId::RevealedType => "revealed-type",
|
||||
DiagnosticId::UnknownRule => "unknown-rule",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_invalid_syntax(&self) -> bool {
|
||||
@@ -654,26 +632,9 @@ impl DiagnosticId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||
pub enum DiagnosticAsStrError {
|
||||
/// The id can't be converted to a string because it belongs to a sub-category.
|
||||
#[error("id from a sub-category: {category}:{name}")]
|
||||
Category {
|
||||
/// The id's category.
|
||||
category: &'static str,
|
||||
/// The diagnostic id in this category.
|
||||
name: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DiagnosticId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.as_str() {
|
||||
Ok(name) => f.write_str(name),
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => {
|
||||
write!(f, "{category}:{name}")
|
||||
}
|
||||
}
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,6 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
|
||||
/// both.)
|
||||
#[derive(Debug)]
|
||||
struct Resolved<'a> {
|
||||
id: String,
|
||||
diagnostics: Vec<ResolvedDiagnostic<'a>>,
|
||||
}
|
||||
|
||||
@@ -152,14 +151,12 @@ impl<'a> Resolved<'a> {
|
||||
for sub in &diag.inner.subs {
|
||||
diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub));
|
||||
}
|
||||
let id = diag.inner.id.to_string();
|
||||
Resolved { id, diagnostics }
|
||||
Resolved { diagnostics }
|
||||
}
|
||||
|
||||
/// Creates a value that is amenable to rendering directly.
|
||||
fn to_renderable(&self, context: usize) -> Renderable<'_> {
|
||||
Renderable {
|
||||
id: &self.id,
|
||||
diagnostics: self
|
||||
.diagnostics
|
||||
.iter()
|
||||
@@ -177,6 +174,7 @@ impl<'a> Resolved<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ResolvedDiagnostic<'a> {
|
||||
severity: Severity,
|
||||
id: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
}
|
||||
@@ -197,20 +195,11 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann)
|
||||
})
|
||||
.collect();
|
||||
let message = if diag.inner.message.as_str().is_empty() {
|
||||
diag.inner.id.to_string()
|
||||
} else {
|
||||
// TODO: See the comment on `Renderable::id` for
|
||||
// a plausible better idea than smushing the ID
|
||||
// into the diagnostic message.
|
||||
format!(
|
||||
"{id}: {message}",
|
||||
id = diag.inner.id,
|
||||
message = diag.inner.message.as_str(),
|
||||
)
|
||||
};
|
||||
let id = Some(diag.inner.id.to_string());
|
||||
let message = diag.inner.message.as_str().to_string();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
id,
|
||||
message,
|
||||
annotations,
|
||||
}
|
||||
@@ -233,6 +222,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.collect();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
id: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
}
|
||||
@@ -299,6 +289,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.sort_by(|snips1, snips2| snips1.has_primary.cmp(&snips2.has_primary).reverse());
|
||||
RenderableDiagnostic {
|
||||
severity: self.severity,
|
||||
id: self.id.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
}
|
||||
@@ -378,20 +369,6 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
/// renderable value. This is usually the lifetime of `Resolved`.
|
||||
#[derive(Debug)]
|
||||
struct Renderable<'r> {
|
||||
// TODO: This is currently unused in the rendering logic below. I'm not
|
||||
// 100% sure yet where I want to put it, but I like what `rustc` does:
|
||||
//
|
||||
// error[E0599]: no method named `sub_builder` <..snip..>
|
||||
//
|
||||
// I believe in order to do this, we'll need to patch it in to
|
||||
// `ruff_annotate_snippets` though. We leave it here for now with that plan
|
||||
// in mind.
|
||||
//
|
||||
// (At time of writing, 2025-03-13, we currently render the diagnostic
|
||||
// ID into the main message of the parent diagnostic. We don't use this
|
||||
// specific field to do that though.)
|
||||
#[expect(dead_code)]
|
||||
id: &'r str,
|
||||
diagnostics: Vec<RenderableDiagnostic<'r>>,
|
||||
}
|
||||
|
||||
@@ -400,6 +377,12 @@ struct Renderable<'r> {
|
||||
struct RenderableDiagnostic<'r> {
|
||||
/// The severity of the diagnostic.
|
||||
severity: Severity,
|
||||
/// The ID of the diagnostic. The ID can usually be used on the CLI or in a
|
||||
/// config file to change the severity of a lint.
|
||||
///
|
||||
/// An ID is always present for top-level diagnostics and always absent for
|
||||
/// sub-diagnostics.
|
||||
id: Option<&'r str>,
|
||||
/// The message emitted with the diagnostic, before any snippets are
|
||||
/// rendered.
|
||||
message: &'r str,
|
||||
@@ -420,7 +403,11 @@ impl RenderableDiagnostic<'_> {
|
||||
.iter()
|
||||
.map(|snippet| snippet.to_annotate(path))
|
||||
});
|
||||
level.title(self.message).snippets(snippets)
|
||||
let mut message = level.title(self.message);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id(id);
|
||||
}
|
||||
message.snippets(snippets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,7 +786,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -823,7 +810,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
warning: lint:test-diagnostic: main diagnostic message
|
||||
warning[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -843,7 +830,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
info: lint:test-diagnostic: main diagnostic message
|
||||
info[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -870,7 +857,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -889,7 +876,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -910,7 +897,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> non-ascii:5:1
|
||||
|
|
||||
3 | ΔΔΔΔΔΔΔΔΔΔΔΔ
|
||||
@@ -929,7 +916,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> non-ascii:2:2
|
||||
|
|
||||
1 | ☃☃☃☃☃☃☃☃☃☃☃☃
|
||||
@@ -953,7 +940,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
4 | dog
|
||||
@@ -970,7 +957,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
5 | elephant
|
||||
@@ -985,7 +972,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1002,7 +989,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:11:1
|
||||
|
|
||||
9 | inchworm
|
||||
@@ -1019,7 +1006,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1052,7 +1039,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1096,7 +1083,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1121,7 +1108,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1149,7 +1136,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1177,7 +1164,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1202,7 +1189,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1233,7 +1220,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1271,7 +1258,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:8:1
|
||||
|
|
||||
7 | dog
|
||||
@@ -1288,7 +1275,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:12:1
|
||||
|
|
||||
11 | gorilla
|
||||
@@ -1306,7 +1293,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:13:1
|
||||
|
|
||||
11 | gorilla
|
||||
@@ -1346,7 +1333,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:3:1
|
||||
|
|
||||
3 | beetle
|
||||
@@ -1375,7 +1362,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1412,7 +1399,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1449,7 +1436,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1477,7 +1464,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1513,7 +1500,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1552,7 +1539,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1600,7 +1587,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1636,7 +1623,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1659,7 +1646,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1679,7 +1666,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1699,7 +1686,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:4
|
||||
|
|
||||
3 | canary
|
||||
@@ -1721,7 +1708,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:4
|
||||
|
|
||||
3 | canary
|
||||
@@ -1753,7 +1740,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:4:1
|
||||
|
|
||||
2 | beetle
|
||||
@@ -1782,7 +1769,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:4:1
|
||||
|
|
||||
2 | beetle
|
||||
@@ -1813,7 +1800,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1848,7 +1835,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1876,7 +1863,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1908,7 +1895,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:3
|
||||
|
|
||||
3 | canary
|
||||
@@ -1930,7 +1917,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:3
|
||||
|
|
||||
3 | canary
|
||||
@@ -1963,7 +1950,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:8:1
|
||||
|
|
||||
6 | finch
|
||||
@@ -2003,7 +1990,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
5 | elephant
|
||||
@@ -2047,7 +2034,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> fruits:1:1
|
||||
|
|
||||
1 | apple
|
||||
@@ -2082,7 +2069,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:11:1
|
||||
|
|
||||
11 | kangaroo
|
||||
|
||||
@@ -179,7 +179,7 @@ fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut
|
||||
let id = format!("{name_key}--{}", arg.get_id());
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!(
|
||||
"<a href=\"#{id}\"<code>{}</code></a>",
|
||||
"<a href=\"#{id}\"><code>{}</code></a>",
|
||||
arg.get_id().to_string().to_uppercase(),
|
||||
));
|
||||
output.push_str("</dt>");
|
||||
|
||||
@@ -134,13 +134,13 @@ impl Set {
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
|
||||
let header_level = if parents.is_empty() { "###" } else { "####" };
|
||||
|
||||
let _ = writeln!(output, "{header_level} [`{name}`]");
|
||||
let _ = writeln!(output, "{header_level} `{name}`");
|
||||
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
output.push_str("!!! warning \"Deprecated\"\n");
|
||||
output.push_str(" This option has been deprecated");
|
||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
||||
output.push_str("> This option has been deprecated");
|
||||
|
||||
if let Some(since) = deprecated.since {
|
||||
write!(output, " in {since}").unwrap();
|
||||
|
||||
@@ -92,7 +92,7 @@ impl std::fmt::Display for IndentStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual width of a indentation.
|
||||
/// The visual width of an indentation.
|
||||
///
|
||||
/// Determines the visual width of a tab character (`\t`) and the number of
|
||||
/// spaces per indent when using [`IndentStyle::Space`].
|
||||
@@ -207,7 +207,7 @@ pub trait FormatOptions {
|
||||
/// What's the max width of a line. Defaults to 80.
|
||||
fn line_width(&self) -> LineWidth;
|
||||
|
||||
/// Derives the print options from the these format options
|
||||
/// Derives the print options from these format options
|
||||
fn as_print_options(&self) -> PrinterOptions;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@ from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.hooks.base_hook import BaseHook
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.providers.mysql.datasets import mysql
|
||||
from airflow.providers.postgres.datasets import postgres
|
||||
from airflow.providers.trino.datasets import trino
|
||||
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
from airflow.sensors.base_sensor_operator import BaseSensorOperator
|
||||
from airflow.triggers.external_task import TaskStateTrigger
|
||||
@@ -78,14 +75,6 @@ BaseHook()
|
||||
# airflow.operators.subdag.*
|
||||
SubDagOperator()
|
||||
|
||||
# airflow.providers.mysql
|
||||
mysql.sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
postgres.sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
trino.sanitize_uri
|
||||
|
||||
# airflow.secrets
|
||||
# get_connection
|
||||
@@ -155,3 +144,18 @@ should_hide_value_for_key
|
||||
from airflow.operators.python import get_current_context
|
||||
|
||||
get_current_context()
|
||||
|
||||
# airflow.providers.mysql
|
||||
from airflow.providers.mysql.datasets.mysql import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
from airflow.providers.postgres.datasets.postgres import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
from airflow.providers.trino.datasets.trino import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from airflow.sdk import Asset
|
||||
from airflow.assets.manager import AssetManager
|
||||
except ModuleNotFoundError:
|
||||
from airflow.datasets import Dataset as Asset
|
||||
from airflow.datasets.manager import DatasetManager as AssetManager
|
||||
|
||||
Asset
|
||||
|
||||
try:
|
||||
from airflow.sdk import Asset
|
||||
except ModuleNotFoundError:
|
||||
from airflow import datasets
|
||||
|
||||
Asset = datasets.Dataset
|
||||
|
||||
asset = Asset()
|
||||
AssetManager()
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from airflow.providers.http.operators.http import HttpOperator
|
||||
except ModuleNotFoundError:
|
||||
from airflow.operators.http_operator import SimpleHttpOperator as HttpOperator
|
||||
|
||||
HttpOperator()
|
||||
@@ -13,6 +13,10 @@ from airflow.decorators import dag, setup, task, task_group, teardown
|
||||
from airflow.io.path import ObjectStoragePath
|
||||
from airflow.io.storage import attach
|
||||
from airflow.models import DAG as DAGFromModel
|
||||
from airflow.models import (
|
||||
Connection,
|
||||
Variable,
|
||||
)
|
||||
from airflow.models.baseoperator import chain, chain_linear, cross_downstream
|
||||
from airflow.models.baseoperatorlink import BaseOperatorLink
|
||||
from airflow.models.dag import DAG as DAGFromDag
|
||||
@@ -42,7 +46,9 @@ ObjectStoragePath()
|
||||
attach()
|
||||
|
||||
# airflow.models
|
||||
Connection()
|
||||
DAGFromModel()
|
||||
Variable()
|
||||
|
||||
# airflow.models.baseoperator
|
||||
chain()
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from airflow.sdk import Asset
|
||||
except ModuleNotFoundError:
|
||||
from airflow.datasets import Dataset as Asset
|
||||
|
||||
Asset()
|
||||
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from airflow.providers.standard.triggers.file import FileTrigger
|
||||
except ModuleNotFoundError:
|
||||
from airflow.triggers.file import FileTrigger
|
||||
|
||||
FileTrigger()
|
||||
@@ -166,3 +166,6 @@ query60 = f"""
|
||||
foo
|
||||
FROM ({user_input}) raw
|
||||
"""
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17967
|
||||
query61 = f"SELECT * FROM table" # skip expressionless f-strings
|
||||
|
||||
@@ -25,3 +25,11 @@ warnings.warn(
|
||||
# some comments here
|
||||
source = None # no trailing comma
|
||||
)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18011
|
||||
warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),))
|
||||
# trigger diagnostic if `skip_file_prefixes` is present and set to the default value
|
||||
warnings.warn("test", skip_file_prefixes=())
|
||||
|
||||
_my_prefixes = ("this","that")
|
||||
warnings.warn("test", skip_file_prefixes = _my_prefixes)
|
||||
|
||||
@@ -28,3 +28,14 @@ abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||
# Some values need to be parenthesized.
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18036
|
||||
# The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
foo(
|
||||
**{
|
||||
# Comment 1
|
||||
"x": 1.0,
|
||||
# Comment 2
|
||||
"y": 2.0,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,3 +12,38 @@ def test_xxx(_fixture): # Error arg
|
||||
|
||||
def test_xxx(*, _fixture): # Error kwonly
|
||||
pass
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17599
|
||||
|
||||
@pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
def test_thingy(_foo): # Ok defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_test_input,_expected",
|
||||
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
|
||||
)
|
||||
def test_eval(_test_input, _expected): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(["_foo", "_bar"], [1, 2, 3])
|
||||
def test_thingy3(_foo, _bar): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(("_foo"), [1, 2, 3])
|
||||
def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize([" _foo", " _bar "], [1, 2, 3])
|
||||
def test_thingy5(_foo, _bar): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
x = "other"
|
||||
@pytest.mark.parametrize(x, [1, 2, 3])
|
||||
def test_thingy5(_foo, _bar): # known false negative, we don't try to resolve variables
|
||||
pass
|
||||
|
||||
@@ -31,7 +31,7 @@ no_sep = None
|
||||
|
||||
" a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
"".split() # []
|
||||
"""
|
||||
"""
|
||||
""".split() # []
|
||||
" ".split() # []
|
||||
"/abc/".split() # ["/abc/"]
|
||||
@@ -73,7 +73,7 @@ r"\n " "\n".split() # [r"\n"]
|
||||
|
||||
# negatives
|
||||
|
||||
# invalid values should not cause panic
|
||||
# invalid values should not cause panic
|
||||
"a,b,c,d".split(maxsplit="hello")
|
||||
"a,b,c,d".split(maxsplit=-"hello")
|
||||
|
||||
@@ -106,3 +106,27 @@ b"TesT.WwW.ExamplE.CoM".split(b".")
|
||||
'''itemC'''
|
||||
"'itemD'"
|
||||
""".split()
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18042
|
||||
print("a,b".rsplit(","))
|
||||
print("a,b,c".rsplit(",", 1))
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18069
|
||||
|
||||
print("".split(maxsplit=0))
|
||||
print("".split(sep=None, maxsplit=0))
|
||||
print(" ".split(maxsplit=0))
|
||||
print(" ".split(sep=None, maxsplit=0))
|
||||
print(" x ".split(maxsplit=0))
|
||||
print(" x ".split(sep=None, maxsplit=0))
|
||||
print(" x ".split(maxsplit=0))
|
||||
print(" x ".split(sep=None, maxsplit=0))
|
||||
print("".rsplit(maxsplit=0))
|
||||
print("".rsplit(sep=None, maxsplit=0))
|
||||
print(" ".rsplit(maxsplit=0))
|
||||
print(" ".rsplit(sep=None, maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
@@ -9,3 +9,7 @@ extensions_dir = "./extensions"
|
||||
glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
|
||||
list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
|
||||
search("*.png")
|
||||
|
||||
# if `dir_fd` is set, suppress the diagnostic
|
||||
glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"), dir_fd=1)
|
||||
list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"), dir_fd=1))
|
||||
|
||||
@@ -87,3 +87,20 @@ def bar(x: int):
|
||||
os.rename("src", "dst", src_dir_fd=3, dst_dir_fd=4)
|
||||
os.rename("src", "dst", src_dir_fd=3)
|
||||
os.rename("src", "dst", dst_dir_fd=4)
|
||||
|
||||
# if `dir_fd` is set, suppress the diagnostic
|
||||
os.readlink(p, dir_fd=1)
|
||||
os.stat(p, dir_fd=2)
|
||||
os.unlink(p, dir_fd=3)
|
||||
os.remove(p, dir_fd=4)
|
||||
os.rmdir(p, dir_fd=5)
|
||||
os.mkdir(p, dir_fd=6)
|
||||
os.chmod(p, dir_fd=7)
|
||||
# `chmod` can also receive a file descriptor in the first argument
|
||||
os.chmod(8)
|
||||
os.chmod(x)
|
||||
|
||||
# if `src_dir_fd` or `dst_dir_fd` are set, suppress the diagnostic
|
||||
os.replace("src", "dst", src_dir_fd=1, dst_dir_fd=2)
|
||||
os.replace("src", "dst", src_dir_fd=1)
|
||||
os.replace("src", "dst", dst_dir_fd=2)
|
||||
|
||||
@@ -144,14 +144,14 @@ def f():
|
||||
|
||||
def f():
|
||||
# make sure that `tmp` is not deleted
|
||||
tmp = 1; result = [] # commment should be protected
|
||||
tmp = 1; result = [] # comment should be protected
|
||||
for i in range(10):
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
# make sure that `tmp` is not deleted
|
||||
result = []; tmp = 1 # commment should be protected
|
||||
result = []; tmp = 1 # comment should be protected
|
||||
for i in range(10):
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
|
||||
@@ -6,14 +6,16 @@ from .mmap import error
|
||||
raise error
|
||||
|
||||
# Testing the modules
|
||||
import socket, mmap, select
|
||||
import socket, mmap, select, resource
|
||||
raise socket.error
|
||||
raise mmap.error
|
||||
raise select.error
|
||||
raise resource.error
|
||||
|
||||
raise socket.error()
|
||||
raise mmap.error(1)
|
||||
raise select.error(1, 2)
|
||||
raise resource.error(1, "strerror", "filename")
|
||||
|
||||
raise socket.error(
|
||||
1,
|
||||
@@ -30,6 +32,9 @@ raise error(1)
|
||||
from select import error
|
||||
raise error(1, 2)
|
||||
|
||||
from resource import error
|
||||
raise error(1, "strerror", "filename")
|
||||
|
||||
# Testing the names
|
||||
raise EnvironmentError
|
||||
raise IOError
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
num = 1337
|
||||
|
||||
def return_num() -> int:
|
||||
@@ -10,6 +13,7 @@ print(bin(num)[2:]) # FURB116
|
||||
print(oct(1337)[2:]) # FURB116
|
||||
print(hex(1337)[2:]) # FURB116
|
||||
print(bin(1337)[2:]) # FURB116
|
||||
print(bin(+1337)[2:]) # FURB116
|
||||
|
||||
print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
@@ -22,3 +26,19 @@ print(hex(0x1337)[3:])
|
||||
# float and complex numbers should be ignored
|
||||
print(bin(1.0)[2:])
|
||||
print(bin(3.14j)[2:])
|
||||
|
||||
d = datetime.datetime.now(tz=datetime.UTC)
|
||||
# autofix is display-only
|
||||
print(bin(d)[2:])
|
||||
# no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
print(bin(len("xyz").numerator)[2:])
|
||||
|
||||
# autofix is display-only
|
||||
print(bin({0: 1}[0].numerator)[2:])
|
||||
# no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
print(bin(ord("\\").numerator)[2:])
|
||||
print(hex(sys
|
||||
.maxunicode)[2:])
|
||||
|
||||
# for negatives numbers autofix is display-only
|
||||
print(bin(-1)[2:])
|
||||
|
||||
@@ -19,6 +19,9 @@ b'c' in b""
|
||||
b"a" in bytearray()
|
||||
b"a" in bytes()
|
||||
1 in frozenset()
|
||||
1 in set(set())
|
||||
2 in frozenset([])
|
||||
'' in set("")
|
||||
|
||||
# OK
|
||||
1 in [2]
|
||||
@@ -35,3 +38,7 @@ b'c' in b"x"
|
||||
b"a" in bytearray([2])
|
||||
b"a" in bytes("a", "utf-8")
|
||||
1 in frozenset("c")
|
||||
1 in set(set((1,2)))
|
||||
1 in set(set([1]))
|
||||
'' in {""}
|
||||
frozenset() in {frozenset()}
|
||||
|
||||
@@ -1116,6 +1116,95 @@ mod tests {
|
||||
PythonVersion::PY310,
|
||||
"InvalidStarExpression"
|
||||
)]
|
||||
#[test_case(
|
||||
"irrefutable_case_pattern_wildcard",
|
||||
"
|
||||
match value:
|
||||
case _:
|
||||
pass
|
||||
case 1:
|
||||
pass
|
||||
",
|
||||
PythonVersion::PY310,
|
||||
"IrrefutableCasePattern"
|
||||
)]
|
||||
#[test_case(
|
||||
"irrefutable_case_pattern_capture",
|
||||
"
|
||||
match value:
|
||||
case irrefutable:
|
||||
pass
|
||||
case 1:
|
||||
pass
|
||||
",
|
||||
PythonVersion::PY310,
|
||||
"IrrefutableCasePattern"
|
||||
)]
|
||||
#[test_case(
|
||||
"single_starred_assignment",
|
||||
"*a = [1, 2, 3, 4]",
|
||||
PythonVersion::PY310,
|
||||
"SingleStarredAssignment"
|
||||
)]
|
||||
#[test_case(
|
||||
"write_to_debug",
|
||||
"
|
||||
__debug__ = False
|
||||
",
|
||||
PythonVersion::PY310,
|
||||
"WriteToDebug"
|
||||
)]
|
||||
#[test_case(
|
||||
"write_to_debug_in_function_param",
|
||||
"
|
||||
def process(__debug__):
|
||||
pass
|
||||
",
|
||||
PythonVersion::PY310,
|
||||
"WriteToDebug"
|
||||
)]
|
||||
#[test_case(
|
||||
"write_to_debug_class_type_param",
|
||||
"
|
||||
class Generic[__debug__]:
|
||||
pass
|
||||
",
|
||||
PythonVersion::PY312,
|
||||
"WriteToDebug"
|
||||
)]
|
||||
#[test_case(
|
||||
"invalid_expression_yield_in_type_param",
|
||||
"
|
||||
type X[T: (yield 1)] = int
|
||||
",
|
||||
PythonVersion::PY312,
|
||||
"InvalidExpression"
|
||||
)]
|
||||
#[test_case(
|
||||
"invalid_expression_yield_in_type_alias",
|
||||
"
|
||||
type Y = (yield 1)
|
||||
",
|
||||
PythonVersion::PY312,
|
||||
"InvalidExpression"
|
||||
)]
|
||||
#[test_case(
|
||||
"invalid_expression_walrus_in_return_annotation",
|
||||
"
|
||||
def f[T](x: int) -> (y := 3): return x
|
||||
",
|
||||
PythonVersion::PY312,
|
||||
"InvalidExpression"
|
||||
)]
|
||||
#[test_case(
|
||||
"invalid_expression_yield_from_in_base_class",
|
||||
"
|
||||
class C[T]((yield from [object])):
|
||||
pass
|
||||
",
|
||||
PythonVersion::PY312,
|
||||
"InvalidExpression"
|
||||
)]
|
||||
fn test_semantic_errors(
|
||||
name: &str,
|
||||
contents: &str,
|
||||
|
||||
@@ -8,13 +8,20 @@ use ruff_python_semantic::SemanticModel;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum Replacement {
|
||||
// There's no replacement or suggestion other than removal
|
||||
None,
|
||||
Name(&'static str),
|
||||
// The attribute name of a class has been changed.
|
||||
AttrName(&'static str),
|
||||
// Additional information. Used when there's replacement but they're not direct mapping.
|
||||
Message(&'static str),
|
||||
// Symbols updated in Airflow 3 with replacement
|
||||
// e.g., `airflow.datasets.Dataset` to `airflow.sdk.Asset`
|
||||
AutoImport {
|
||||
module: &'static str,
|
||||
name: &'static str,
|
||||
},
|
||||
// Symbols updated in Airflow 3 with only module changed. Used when we want to match multiple names.
|
||||
// e.g., `airflow.configuration.as_dict | get` to `airflow.configuration.conf.as_dict | get`
|
||||
SourceModuleMoved {
|
||||
module: &'static str,
|
||||
name: String,
|
||||
@@ -45,7 +52,8 @@ pub(crate) enum ProviderReplacement {
|
||||
|
||||
pub(crate) fn is_guarded_by_try_except(
|
||||
expr: &Expr,
|
||||
replacement: &Replacement,
|
||||
module: &str,
|
||||
name: &str,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
match expr {
|
||||
@@ -63,7 +71,7 @@ pub(crate) fn is_guarded_by_try_except(
|
||||
if !suspended_exceptions.contains(Exceptions::ATTRIBUTE_ERROR) {
|
||||
return false;
|
||||
}
|
||||
try_block_contains_undeprecated_attribute(try_node, replacement, semantic)
|
||||
try_block_contains_undeprecated_attribute(try_node, module, name, semantic)
|
||||
}
|
||||
Expr::Name(ExprName { id, .. }) => {
|
||||
let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else {
|
||||
@@ -89,7 +97,7 @@ pub(crate) fn is_guarded_by_try_except(
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try_block_contains_undeprecated_import(try_node, replacement)
|
||||
try_block_contains_undeprecated_import(try_node, module, name)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
@@ -100,12 +108,10 @@ pub(crate) fn is_guarded_by_try_except(
|
||||
/// member is being accessed from the non-deprecated location?
|
||||
fn try_block_contains_undeprecated_attribute(
|
||||
try_node: &StmtTry,
|
||||
replacement: &Replacement,
|
||||
module: &str,
|
||||
name: &str,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
let Replacement::AutoImport { module, name } = replacement else {
|
||||
return false;
|
||||
};
|
||||
let undeprecated_qualified_name = {
|
||||
let mut builder = QualifiedNameBuilder::default();
|
||||
for part in module.split('.') {
|
||||
@@ -122,10 +128,7 @@ fn try_block_contains_undeprecated_attribute(
|
||||
/// Given an [`ast::StmtTry`] node, does the `try` branch of that node
|
||||
/// contain any [`ast::StmtImportFrom`] nodes that indicate the airflow
|
||||
/// member is being imported from the non-deprecated location?
|
||||
fn try_block_contains_undeprecated_import(try_node: &StmtTry, replacement: &Replacement) -> bool {
|
||||
let Replacement::AutoImport { module, name } = replacement else {
|
||||
return false;
|
||||
};
|
||||
fn try_block_contains_undeprecated_import(try_node: &StmtTry, module: &str, name: &str) -> bool {
|
||||
let mut import_searcher = ImportSearcher::new(module, name);
|
||||
import_searcher.visit_body(&try_node.body);
|
||||
import_searcher.found_import
|
||||
|
||||
@@ -46,9 +46,12 @@ mod tests {
|
||||
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_sqlite.py"))]
|
||||
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_zendesk.py"))]
|
||||
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_standard.py"))]
|
||||
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_try.py"))]
|
||||
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))]
|
||||
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))]
|
||||
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_try.py"))]
|
||||
#[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312.py"))]
|
||||
#[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312_try.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::rules::airflow::helpers::ProviderReplacement;
|
||||
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{Expr, ExprAttribute};
|
||||
@@ -937,13 +937,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
ranged.range(),
|
||||
);
|
||||
|
||||
if let ProviderReplacement::AutoImport {
|
||||
module,
|
||||
name,
|
||||
provider: _,
|
||||
version: _,
|
||||
} = replacement
|
||||
{
|
||||
let semantic = checker.semantic();
|
||||
if let Some((module, name)) = match &replacement {
|
||||
ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)),
|
||||
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
|
||||
Some((module, name.as_str()))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
if is_guarded_by_try_except(expr, module, name, semantic) {
|
||||
return;
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from(module, name),
|
||||
@@ -954,6 +958,5 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
Ok(Fix::safe_edits(import_edit, [replacement_edit]))
|
||||
});
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
|
||||
@@ -57,28 +57,27 @@ impl Violation for Airflow3Removal {
|
||||
} = self;
|
||||
match replacement {
|
||||
Replacement::None
|
||||
| Replacement::Name(_)
|
||||
| Replacement::AttrName(_)
|
||||
| Replacement::Message(_)
|
||||
| Replacement::AutoImport { module: _, name: _ }
|
||||
| Replacement::SourceModuleMoved { module: _, name: _ } => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0")
|
||||
}
|
||||
Replacement::Message(message) => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0; {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Airflow3Removal { replacement, .. } = self;
|
||||
match replacement {
|
||||
Replacement::Name(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::None => None,
|
||||
Replacement::AttrName(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::Message(message) => Some((*message).to_string()),
|
||||
Replacement::AutoImport { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
Replacement::SourceModuleMoved { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,22 +277,22 @@ fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) {
|
||||
|
||||
let replacement = match *qualname.segments() {
|
||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||
"dataset_factories" => Replacement::Name("asset_factories"),
|
||||
"dataset_uri_handlers" => Replacement::Name("asset_uri_handlers"),
|
||||
"dataset_factories" => Replacement::AttrName("asset_factories"),
|
||||
"dataset_uri_handlers" => Replacement::AttrName("asset_uri_handlers"),
|
||||
"dataset_to_openlineage_converters" => {
|
||||
Replacement::Name("asset_to_openlineage_converters")
|
||||
Replacement::AttrName("asset_to_openlineage_converters")
|
||||
}
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "lineage", "hook", "DatasetLineageInfo"] => match attr.as_str() {
|
||||
"dataset" => Replacement::Name("asset"),
|
||||
"dataset" => Replacement::AttrName("asset"),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Create the `Fix` first to avoid cloning `Replacement`.
|
||||
let fix = if let Replacement::Name(name) = replacement {
|
||||
let fix = if let Replacement::AttrName(name) = replacement {
|
||||
Some(Fix::safe_edit(Edit::range_replacement(
|
||||
name.to_string(),
|
||||
attr.range(),
|
||||
@@ -466,52 +465,52 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
|
||||
|
||||
let replacement = match qualname.segments() {
|
||||
["airflow", "datasets", "manager", "DatasetManager"] => match attr.as_str() {
|
||||
"register_dataset_change" => Replacement::Name("register_asset_change"),
|
||||
"create_datasets" => Replacement::Name("create_assets"),
|
||||
"notify_dataset_created" => Replacement::Name("notify_asset_created"),
|
||||
"notify_dataset_changed" => Replacement::Name("notify_asset_changed"),
|
||||
"notify_dataset_alias_created" => Replacement::Name("notify_asset_alias_created"),
|
||||
"register_dataset_change" => Replacement::AttrName("register_asset_change"),
|
||||
"create_datasets" => Replacement::AttrName("create_assets"),
|
||||
"notify_dataset_created" => Replacement::AttrName("notify_asset_created"),
|
||||
"notify_dataset_changed" => Replacement::AttrName("notify_asset_changed"),
|
||||
"notify_dataset_alias_created" => Replacement::AttrName("notify_asset_alias_created"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "lineage", "hook", "HookLineageCollector"] => match attr.as_str() {
|
||||
"create_dataset" => Replacement::Name("create_asset"),
|
||||
"add_input_dataset" => Replacement::Name("add_input_asset"),
|
||||
"add_output_dataset" => Replacement::Name("add_output_asset"),
|
||||
"collected_datasets" => Replacement::Name("collected_assets"),
|
||||
"create_dataset" => Replacement::AttrName("create_asset"),
|
||||
"add_input_dataset" => Replacement::AttrName("add_input_asset"),
|
||||
"add_output_dataset" => Replacement::AttrName("add_output_asset"),
|
||||
"collected_datasets" => Replacement::AttrName("collected_assets"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "providers", "amazon", "auth_manager", "aws_auth_manager", "AwsAuthManager"] => {
|
||||
match attr.as_str() {
|
||||
"is_authorized_dataset" => Replacement::Name("is_authorized_asset"),
|
||||
"is_authorized_dataset" => Replacement::AttrName("is_authorized_asset"),
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||
"initialize_providers_dataset_uri_resources" => {
|
||||
Replacement::Name("initialize_providers_asset_uri_resources")
|
||||
Replacement::AttrName("initialize_providers_asset_uri_resources")
|
||||
}
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "secrets", "local_filesystem", "LocalFilesystemBackend"] => match attr.as_str()
|
||||
{
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "datasets", ..] | ["airflow", "Dataset"] => match attr.as_str() {
|
||||
"iter_datasets" => Replacement::Name("iter_assets"),
|
||||
"iter_dataset_aliases" => Replacement::Name("iter_asset_aliases"),
|
||||
"iter_datasets" => Replacement::AttrName("iter_assets"),
|
||||
"iter_dataset_aliases" => Replacement::AttrName("iter_asset_aliases"),
|
||||
_ => return,
|
||||
},
|
||||
segments => {
|
||||
if is_airflow_secret_backend(segments) {
|
||||
match attr.as_str() {
|
||||
"get_conn_uri" => Replacement::Name("get_conn_value"),
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_conn_uri" => Replacement::AttrName("get_conn_value"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
}
|
||||
} else if is_airflow_hook(segments) {
|
||||
match attr.as_str() {
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
}
|
||||
} else {
|
||||
@@ -520,7 +519,7 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
|
||||
}
|
||||
};
|
||||
// Create the `Fix` first to avoid cloning `Replacement`.
|
||||
let fix = if let Replacement::Name(name) = replacement {
|
||||
let fix = if let Replacement::AttrName(name) = replacement {
|
||||
Some(Fix::safe_edit(Edit::range_replacement(
|
||||
name.to_string(),
|
||||
attr.range(),
|
||||
@@ -566,12 +565,12 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
let replacement = match qualified_name.segments() {
|
||||
// airflow.PY\d{1,2}
|
||||
["airflow", "PY36" | "PY37" | "PY38" | "PY39" | "PY310" | "PY311" | "PY312"] => {
|
||||
Replacement::Name("sys.version_info")
|
||||
Replacement::Message("Use `sys.version_info` instead")
|
||||
}
|
||||
|
||||
// airflow.api_connexion.security
|
||||
["airflow", "api_connexion", "security", "requires_access"] => {
|
||||
Replacement::Name("airflow.api_connexion.security.requires_access_*")
|
||||
Replacement::Message("Use `airflow.api_connexion.security.requires_access_*` instead")
|
||||
}
|
||||
["airflow", "api_connexion", "security", "requires_access_dataset"] => {
|
||||
Replacement::AutoImport {
|
||||
@@ -626,9 +625,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
["airflow", "datasets", "DatasetAliasEvent"] => Replacement::None,
|
||||
|
||||
// airflow.hooks
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => {
|
||||
Replacement::Name("airflow.hooks.base.BaseHook")
|
||||
}
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => Replacement::AutoImport {
|
||||
module: "airflow.hooks.base",
|
||||
name: "BaseHook",
|
||||
},
|
||||
|
||||
// airflow.lineage.hook
|
||||
["airflow", "lineage", "hook", "DatasetLineageInfo"] => Replacement::AutoImport {
|
||||
@@ -664,9 +664,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
},
|
||||
|
||||
// airflow.notifications
|
||||
["airflow", "notifications", "basenotifier", "BaseNotifier"] => {
|
||||
Replacement::Name("airflow.sdk.BaseNotifier")
|
||||
}
|
||||
["airflow", "notifications", "basenotifier", "BaseNotifier"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "BaseNotifier",
|
||||
},
|
||||
|
||||
// airflow.operators
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
@@ -691,7 +692,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.sensors
|
||||
["airflow", "sensors", "base_sensor_operator", "BaseSensorOperator"] => {
|
||||
Replacement::Name("airflow.sdk.bases.sensor.BaseSensorOperator")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.sdk.bases.sensor",
|
||||
name: "BaseSensorOperator",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.timetables
|
||||
@@ -720,23 +724,36 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.utils.dates
|
||||
["dates", "date_range"] => Replacement::None,
|
||||
["dates", "days_ago"] => Replacement::Name("pendulum.today('UTC').add(days=-N, ...)"),
|
||||
["dates", "days_ago"] => {
|
||||
Replacement::Message("Use `pendulum.today('UTC').add(days=-N, ...)` instead")
|
||||
}
|
||||
["dates", "parse_execution_date" | "round_time" | "scale_time_units" | "infer_time_unit"] => {
|
||||
Replacement::None
|
||||
}
|
||||
|
||||
// airflow.utils.file
|
||||
["file", "TemporaryDirectory"] => Replacement::Name("tempfile.TemporaryDirectory"),
|
||||
["file", "mkdirs"] => Replacement::Name("pathlib.Path({path}).mkdir"),
|
||||
["file", "TemporaryDirectory"] => Replacement::AutoImport {
|
||||
module: "tempfile",
|
||||
name: "TemporaryDirectory",
|
||||
},
|
||||
["file", "mkdirs"] => Replacement::Message("Use `pathlib.Path({path}).mkdir` instead"),
|
||||
|
||||
// airflow.utils.helpers
|
||||
["helpers", "chain"] => Replacement::Name("airflow.sdk.chain"),
|
||||
["helpers", "cross_downstream"] => Replacement::Name("airflow.sdk.cross_downstream"),
|
||||
["helpers", "chain"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "chain",
|
||||
},
|
||||
["helpers", "cross_downstream"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "cross_downstream",
|
||||
},
|
||||
|
||||
// TODO: update it as SourceModuleMoved
|
||||
// airflow.utils.log.secrets_masker
|
||||
["log", "secrets_masker"] => {
|
||||
Replacement::Name("airflow.sdk.execution_time.secrets_masker")
|
||||
}
|
||||
["log", "secrets_masker"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk.execution_time",
|
||||
name: "secrets_masker",
|
||||
},
|
||||
|
||||
// airflow.utils.state
|
||||
["state", "SHUTDOWN" | "terminating_states"] => Replacement::None,
|
||||
@@ -751,18 +768,20 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
// airflow.www
|
||||
// TODO: www has been removed
|
||||
["airflow", "www", "auth", "has_access"] => {
|
||||
Replacement::Name("airflow.www.auth.has_access_*")
|
||||
Replacement::Message("Use `airflow.www.auth.has_access_*` instead")
|
||||
}
|
||||
["airflow", "www", "auth", "has_access_dataset"] => Replacement::AutoImport {
|
||||
module: "airflow.www.auth",
|
||||
name: "has_access_asset",
|
||||
},
|
||||
["airflow", "www", "utils", "get_sensitive_variables_fields"] => {
|
||||
Replacement::Name("airflow.utils.log.secrets_masker.get_sensitive_variables_fields")
|
||||
}
|
||||
["airflow", "www", "utils", "should_hide_value_for_key"] => {
|
||||
Replacement::Name("airflow.utils.log.secrets_masker.should_hide_value_for_key")
|
||||
}
|
||||
["airflow", "www", "utils", "get_sensitive_variables_fields"] => Replacement::AutoImport {
|
||||
module: "airflow.utils.log.secrets_masker",
|
||||
name: "get_sensitive_variables_fields",
|
||||
},
|
||||
["airflow", "www", "utils", "should_hide_value_for_key"] => Replacement::AutoImport {
|
||||
module: "airflow.utils.log.secrets_masker",
|
||||
name: "should_hide_value_for_key",
|
||||
},
|
||||
|
||||
// airflow.providers.amazon
|
||||
["airflow", "providers", "amazon", "aws", "datasets", "s3", rest] => match *rest {
|
||||
@@ -774,9 +793,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.amazon.aws.assets.s3",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
"sanitize_uri" => {
|
||||
Replacement::Name("airflow.providers.amazon.aws.assets.s3.sanitize_uri")
|
||||
}
|
||||
"sanitize_uri" => Replacement::AutoImport {
|
||||
module: "airflow.providers.amazon.aws.assets.s3",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "providers", "amazon", "aws", "auth_manager", "avp", "entities", "AvpEntities", "DATASET"] => {
|
||||
@@ -797,9 +817,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.common.io.assets.file",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
"sanitize_uri" => {
|
||||
Replacement::Name("airflow.providers.common.io.assets.file.sanitize_uri")
|
||||
}
|
||||
"sanitize_uri" => Replacement::AutoImport {
|
||||
module: "airflow.providers.common.io.assets.file",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
|
||||
@@ -826,20 +847,28 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.google.assets.gcs",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
["gcs", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.google.assets.gcs.sanitize_uri")
|
||||
}
|
||||
["gcs", "sanitize_uri"] => Replacement::AutoImport {
|
||||
module: "airflow.providers.google.assets.gcs",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
|
||||
_ => return,
|
||||
},
|
||||
|
||||
// airflow.providers.mysql
|
||||
["airflow", "providers", "mysql", "datasets", "mysql", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.mysql.assets.mysql.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.mysql.assets.mysql",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.providers.postgres
|
||||
["airflow", "providers", "postgres", "datasets", "postgres", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.postgres.assets.postgres.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.postgres.assets.postgres",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.providers.openlineage
|
||||
@@ -859,16 +888,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.providers.trino
|
||||
["airflow", "providers", "trino", "datasets", "trino", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.trino.assets.trino.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.trino.assets.trino",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if is_guarded_by_try_except(expr, &replacement, semantic) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
Airflow3Removal {
|
||||
deprecated: qualified_name.to_string(),
|
||||
@@ -876,8 +904,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
},
|
||||
range,
|
||||
);
|
||||
|
||||
if let Replacement::AutoImport { module, name } = replacement {
|
||||
let semantic = checker.semantic();
|
||||
if let Some((module, name)) = match &replacement {
|
||||
Replacement::AutoImport { module, name } => Some((module, *name)),
|
||||
Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())),
|
||||
_ => None,
|
||||
} {
|
||||
if is_guarded_by_try_except(expr, module, name, semantic) {
|
||||
return;
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from(module, name),
|
||||
@@ -946,7 +981,7 @@ fn diagnostic_for_argument(
|
||||
Airflow3Removal {
|
||||
deprecated: deprecated.to_string(),
|
||||
replacement: match replacement {
|
||||
Some(name) => Replacement::Name(name),
|
||||
Some(name) => Replacement::AttrName(name),
|
||||
None => Replacement::None,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::importer::ImportRequest;
|
||||
|
||||
use crate::rules::airflow::helpers::ProviderReplacement;
|
||||
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{Expr, ExprAttribute};
|
||||
@@ -279,13 +279,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
ranged.range(),
|
||||
);
|
||||
|
||||
if let ProviderReplacement::AutoImport {
|
||||
module,
|
||||
name,
|
||||
provider: _,
|
||||
version: _,
|
||||
} = replacement
|
||||
{
|
||||
let semantic = checker.semantic();
|
||||
if let Some((module, name)) = match &replacement {
|
||||
ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)),
|
||||
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
|
||||
Some((module, name.as_str()))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
if is_guarded_by_try_except(expr, module, name, semantic) {
|
||||
return;
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from(module, name),
|
||||
|
||||
@@ -53,7 +53,8 @@ impl Violation for Airflow3SuggestedUpdate {
|
||||
} = self;
|
||||
match replacement {
|
||||
Replacement::None
|
||||
| Replacement::Name(_)
|
||||
| Replacement::AttrName(_)
|
||||
| Replacement::Message(_)
|
||||
| Replacement::AutoImport { module: _, name: _ }
|
||||
| Replacement::SourceModuleMoved { module: _, name: _ } => {
|
||||
format!(
|
||||
@@ -61,27 +62,21 @@ impl Violation for Airflow3SuggestedUpdate {
|
||||
It still works in Airflow 3.0 but is expected to be removed in a future version."
|
||||
)
|
||||
}
|
||||
Replacement::Message(message) => {
|
||||
format!(
|
||||
"`{deprecated}` is removed in Airflow 3.0; \
|
||||
It still works in Airflow 3.0 but is expected to be removed in a future version.; \
|
||||
{message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Airflow3SuggestedUpdate { replacement, .. } = self;
|
||||
match replacement {
|
||||
Replacement::Name(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::None => None,
|
||||
Replacement::AttrName(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::Message(message) => Some((*message).to_string()),
|
||||
Replacement::AutoImport { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
Replacement::SourceModuleMoved { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +121,7 @@ fn diagnostic_for_argument(
|
||||
Airflow3SuggestedUpdate {
|
||||
deprecated: deprecated.to_string(),
|
||||
replacement: match replacement {
|
||||
Some(name) => Replacement::Name(name),
|
||||
Some(name) => Replacement::AttrName(name),
|
||||
None => Replacement::None,
|
||||
},
|
||||
},
|
||||
@@ -242,6 +237,14 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
name: "attach".to_string(),
|
||||
},
|
||||
|
||||
// airflow.models
|
||||
["airflow", "models", rest @ ("Connection" | "Variable")] => {
|
||||
Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.models.baseoperator
|
||||
["airflow", "models", "baseoperator", rest] => match *rest {
|
||||
"chain" | "chain_linear" | "cross_downstream" => Replacement::SourceModuleMoved {
|
||||
@@ -275,10 +278,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if is_guarded_by_try_except(expr, &replacement, semantic) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
Airflow3SuggestedUpdate {
|
||||
deprecated: qualified_name.to_string(),
|
||||
@@ -287,7 +286,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
range,
|
||||
);
|
||||
|
||||
if let Replacement::AutoImport { module, name } = replacement {
|
||||
let semantic = checker.semantic();
|
||||
if let Some((module, name)) = match &replacement {
|
||||
Replacement::AutoImport { module, name } => Some((module, *name)),
|
||||
Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())),
|
||||
_ => None,
|
||||
} {
|
||||
if is_guarded_by_try_except(expr, module, name, semantic) {
|
||||
return;
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from(module, name),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0
|
||||
|
|
||||
5 | name = "test_plugin"
|
||||
6 | # --- Invalid extensions start
|
||||
@@ -10,8 +10,9 @@ AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0
|
||||
|
|
||||
6 | # --- Invalid extensions start
|
||||
7 | operators = [PluginOperator]
|
||||
@@ -20,8 +21,9 @@ AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This e
|
||||
9 | hooks = [PluginHook]
|
||||
10 | executors = [PluginExecutor]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0
|
||||
|
|
||||
7 | operators = [PluginOperator]
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
@@ -30,8 +32,9 @@ AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This ext
|
||||
10 | executors = [PluginExecutor]
|
||||
11 | # --- Invalid extensions end
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0
|
||||
|
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
@@ -40,3 +43,4 @@ AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; Thi
|
||||
11 | # --- Invalid extensions end
|
||||
12 | macros = [plugin_macro]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
@@ -258,10 +258,11 @@ AIR301_args.py:90:16: AIR301 `filename_template` is removed in Airflow 3.0
|
||||
92 | FabAuthManager(None)
|
||||
|
|
||||
|
||||
AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now
|
||||
AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0
|
||||
|
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
91 |
|
||||
92 | FabAuthManager(None)
|
||||
| ^^^^^^ AIR301
|
||||
|
|
||||
= help: The constructor takes no parameter now
|
||||
|
||||
@@ -1,448 +1,488 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR301_names.py:56:1: AIR301 `airflow.PY36` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:1: AIR301 `airflow.PY36` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:7: AIR301 `airflow.PY37` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:7: AIR301 `airflow.PY37` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:13: AIR301 `airflow.PY38` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:13: AIR301 `airflow.PY38` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:19: AIR301 `airflow.PY39` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:19: AIR301 `airflow.PY39` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:25: AIR301 `airflow.PY310` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:25: AIR301 `airflow.PY310` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:32: AIR301 `airflow.PY311` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:32: AIR301 `airflow.PY311` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:39: AIR301 `airflow.PY312` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:39: AIR301 `airflow.PY312` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:59:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
|
||||
AIR301_names.py:56:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
|
||||
|
|
||||
58 | # airflow.api_connexion.security
|
||||
59 | requires_access
|
||||
55 | # airflow.api_connexion.security
|
||||
56 | requires_access
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.api_connexion.security.requires_access_*` instead
|
||||
|
||||
AIR301_names.py:63:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.get` instead
|
||||
|
||||
AIR301_names.py:63:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getboolean` instead
|
||||
|
||||
AIR301_names.py:63:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getfloat` instead
|
||||
|
||||
AIR301_names.py:63:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getint` instead
|
||||
|
||||
AIR301_names.py:63:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.has_option` instead
|
||||
|
||||
AIR301_names.py:63:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.remove_option` instead
|
||||
|
||||
AIR301_names.py:63:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.as_dict` instead
|
||||
|
||||
AIR301_names.py:63:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.set` instead
|
||||
|
||||
AIR301_names.py:67:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0; The whole `airflow.contrib` module has been removed.
|
||||
AIR301_names.py:64:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
|
||||
|
|
||||
66 | # airflow.contrib.*
|
||||
67 | AWSAthenaHook()
|
||||
63 | # airflow.contrib.*
|
||||
64 | AWSAthenaHook()
|
||||
| ^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: The whole `airflow.contrib` module has been removed.
|
||||
|
||||
AIR301_names.py:71:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
AIR301_names.py:68:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
|
|
||||
70 | # airflow.datasets
|
||||
71 | DatasetAliasEvent()
|
||||
67 | # airflow.datasets
|
||||
68 | DatasetAliasEvent()
|
||||
| ^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:75:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
|
||||
AIR301_names.py:72:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
|
||||
|
|
||||
74 | # airflow.hooks
|
||||
75 | BaseHook()
|
||||
71 | # airflow.hooks
|
||||
72 | BaseHook()
|
||||
| ^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.hooks.base.BaseHook` instead
|
||||
|
||||
AIR301_names.py:79:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0; The whole `airflow.subdag` module has been removed.
|
||||
AIR301_names.py:76:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
|
||||
|
|
||||
78 | # airflow.operators.subdag.*
|
||||
79 | SubDagOperator()
|
||||
75 | # airflow.operators.subdag.*
|
||||
76 | SubDagOperator()
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
80 |
|
||||
81 | # airflow.providers.mysql
|
||||
|
|
||||
= help: The whole `airflow.subdag` module has been removed.
|
||||
|
||||
AIR301_names.py:82:7: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
|
||||
AIR301_names.py:85:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
|
||||
|
|
||||
81 | # airflow.providers.mysql
|
||||
82 | mysql.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
83 |
|
||||
84 | # airflow.providers.postgres
|
||||
|
|
||||
= help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:85:10: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
84 | # airflow.providers.postgres
|
||||
85 | postgres.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
86 |
|
||||
87 | # airflow.providers.trino
|
||||
|
|
||||
= help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:88:7: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
87 | # airflow.providers.trino
|
||||
88 | trino.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
89 |
|
||||
90 | # airflow.secrets
|
||||
|
|
||||
= help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:96:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
|
||||
|
|
||||
95 | # airflow.sensors.base_sensor_operator
|
||||
96 | BaseSensorOperator()
|
||||
84 | # airflow.sensors.base_sensor_operator
|
||||
85 | BaseSensorOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead
|
||||
|
||||
AIR301_names.py:100:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
AIR301_names.py:89:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
|
|
||||
88 | # airflow.triggers.external_task
|
||||
89 | TaskStateTrigger()
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
90 |
|
||||
91 | # airflow.utils.date
|
||||
|
|
||||
|
||||
AIR301_names.py:92:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
91 | # airflow.utils.date
|
||||
92 | dates.date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
93 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301_names.py:93:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
91 | # airflow.utils.date
|
||||
92 | dates.date_range
|
||||
93 | dates.days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
94 |
|
||||
95 | date_range
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:95:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
93 | dates.days_ago
|
||||
94 |
|
||||
95 | date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301_names.py:96:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
95 | date_range
|
||||
96 | days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:97:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
|
|
||||
95 | date_range
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
|
|
||||
|
||||
AIR301_names.py:98:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
99 | # airflow.triggers.external_task
|
||||
100 | TaskStateTrigger()
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
99 | round_time
|
||||
100 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:99:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
|
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
| ^^^^^^^^^^ AIR301
|
||||
100 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:100:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
|
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
100 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
101 |
|
||||
102 | # airflow.utils.date
|
||||
102 | # This one was not deprecated.
|
||||
|
|
||||
|
||||
AIR301_names.py:103:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
AIR301_names.py:107:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
|
|
||||
102 | # airflow.utils.date
|
||||
103 | dates.date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
104 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301_names.py:104:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
102 | # airflow.utils.date
|
||||
103 | dates.date_range
|
||||
104 | dates.days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
105 |
|
||||
106 | date_range
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:106:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
104 | dates.days_ago
|
||||
105 |
|
||||
106 | date_range
|
||||
106 | # airflow.utils.dag_cycle_tester
|
||||
107 | test_cycle
|
||||
| ^^^^^^^^^^ AIR301
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301_names.py:107:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
AIR301_names.py:111:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
|
|
||||
106 | date_range
|
||||
107 | days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:108:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
|
|
||||
106 | date_range
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
|
|
||||
|
||||
AIR301_names.py:109:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
110 | round_time
|
||||
111 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:110:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
|
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
| ^^^^^^^^^^ AIR301
|
||||
111 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:111:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
|
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
111 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
110 | # airflow.utils.db
|
||||
111 | create_session
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
112 |
|
||||
113 | # This one was not deprecated.
|
||||
113 | # airflow.utils.decorators
|
||||
|
|
||||
|
||||
AIR301_names.py:118:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
AIR301_names.py:114:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
|
||||
|
|
||||
117 | # airflow.utils.dag_cycle_tester
|
||||
118 | test_cycle
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:122:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
|
|
||||
121 | # airflow.utils.db
|
||||
122 | create_session
|
||||
113 | # airflow.utils.decorators
|
||||
114 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
123 |
|
||||
124 | # airflow.utils.decorators
|
||||
115 |
|
||||
116 | # airflow.utils.file
|
||||
|
|
||||
= help: `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
|
||||
AIR301_names.py:125:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0; `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
AIR301_names.py:117:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
|
||||
|
|
||||
124 | # airflow.utils.decorators
|
||||
125 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
126 |
|
||||
127 | # airflow.utils.file
|
||||
|
|
||||
|
||||
AIR301_names.py:128:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
|
||||
|
|
||||
127 | # airflow.utils.file
|
||||
128 | TemporaryDirectory()
|
||||
116 | # airflow.utils.file
|
||||
117 | TemporaryDirectory()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
129 | mkdirs
|
||||
118 | mkdirs
|
||||
|
|
||||
= help: Use `tempfile.TemporaryDirectory` instead
|
||||
|
||||
AIR301_names.py:129:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
AIR301_names.py:118:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
|
|
||||
127 | # airflow.utils.file
|
||||
128 | TemporaryDirectory()
|
||||
129 | mkdirs
|
||||
116 | # airflow.utils.file
|
||||
117 | TemporaryDirectory()
|
||||
118 | mkdirs
|
||||
| ^^^^^^ AIR301
|
||||
130 |
|
||||
131 | # airflow.utils.helpers
|
||||
119 |
|
||||
120 | # airflow.utils.helpers
|
||||
|
|
||||
= help: Use `pathlib.Path({path}).mkdir` instead
|
||||
|
||||
AIR301_names.py:132:1: AIR301 `airflow.utils.helpers.chain` is removed in Airflow 3.0
|
||||
AIR301_names.py:121:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0
|
||||
|
|
||||
131 | # airflow.utils.helpers
|
||||
132 | helper_chain
|
||||
120 | # airflow.utils.helpers
|
||||
121 | helper_chain
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
133 | helper_cross_downstream
|
||||
122 | helper_cross_downstream
|
||||
|
|
||||
= help: Use `airflow.sdk.chain` instead
|
||||
|
||||
AIR301_names.py:133:1: AIR301 `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
|
||||
ℹ Safe fix
|
||||
48 48 | from airflow.utils.trigger_rule import TriggerRule
|
||||
49 49 | from airflow.www.auth import has_access
|
||||
50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
51 |+from airflow.sdk import chain
|
||||
51 52 |
|
||||
52 53 | # airflow root
|
||||
53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
--------------------------------------------------------------------------------
|
||||
118 119 | mkdirs
|
||||
119 120 |
|
||||
120 121 | # airflow.utils.helpers
|
||||
121 |-helper_chain
|
||||
122 |+chain
|
||||
122 123 | helper_cross_downstream
|
||||
123 124 |
|
||||
124 125 | # airflow.utils.log
|
||||
|
||||
AIR301_names.py:122:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
|
||||
|
|
||||
131 | # airflow.utils.helpers
|
||||
132 | helper_chain
|
||||
133 | helper_cross_downstream
|
||||
120 | # airflow.utils.helpers
|
||||
121 | helper_chain
|
||||
122 | helper_cross_downstream
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
134 |
|
||||
135 | # airflow.utils.log
|
||||
123 |
|
||||
124 | # airflow.utils.log
|
||||
|
|
||||
= help: Use `airflow.sdk.cross_downstream` instead
|
||||
|
||||
AIR301_names.py:136:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
|
||||
ℹ Safe fix
|
||||
48 48 | from airflow.utils.trigger_rule import TriggerRule
|
||||
49 49 | from airflow.www.auth import has_access
|
||||
50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
51 |+from airflow.sdk import cross_downstream
|
||||
51 52 |
|
||||
52 53 | # airflow root
|
||||
53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
--------------------------------------------------------------------------------
|
||||
119 120 |
|
||||
120 121 | # airflow.utils.helpers
|
||||
121 122 | helper_chain
|
||||
122 |-helper_cross_downstream
|
||||
123 |+cross_downstream
|
||||
123 124 |
|
||||
124 125 | # airflow.utils.log
|
||||
125 126 | secrets_masker
|
||||
|
||||
AIR301_names.py:125:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
|
||||
|
|
||||
135 | # airflow.utils.log
|
||||
136 | secrets_masker
|
||||
124 | # airflow.utils.log
|
||||
125 | secrets_masker
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
137 |
|
||||
138 | # airflow.utils.state
|
||||
126 |
|
||||
127 | # airflow.utils.state
|
||||
|
|
||||
= help: Use `airflow.sdk.execution_time.secrets_masker` instead
|
||||
|
||||
AIR301_names.py:139:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
AIR301_names.py:128:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
|
|
||||
138 | # airflow.utils.state
|
||||
139 | SHUTDOWN
|
||||
127 | # airflow.utils.state
|
||||
128 | SHUTDOWN
|
||||
| ^^^^^^^^ AIR301
|
||||
140 | terminating_states
|
||||
129 | terminating_states
|
||||
|
|
||||
|
||||
AIR301_names.py:140:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
AIR301_names.py:129:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
|
|
||||
138 | # airflow.utils.state
|
||||
139 | SHUTDOWN
|
||||
140 | terminating_states
|
||||
127 | # airflow.utils.state
|
||||
128 | SHUTDOWN
|
||||
129 | terminating_states
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
141 |
|
||||
142 | # airflow.utils.trigger_rule
|
||||
130 |
|
||||
131 | # airflow.utils.trigger_rule
|
||||
|
|
||||
|
||||
AIR301_names.py:143:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
AIR301_names.py:132:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
|
|
||||
142 | # airflow.utils.trigger_rule
|
||||
143 | TriggerRule.DUMMY
|
||||
131 | # airflow.utils.trigger_rule
|
||||
132 | TriggerRule.DUMMY
|
||||
| ^^^^^ AIR301
|
||||
144 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
133 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
|
|
||||
|
||||
AIR301_names.py:144:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
AIR301_names.py:133:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
|
|
||||
142 | # airflow.utils.trigger_rule
|
||||
143 | TriggerRule.DUMMY
|
||||
144 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
131 | # airflow.utils.trigger_rule
|
||||
132 | TriggerRule.DUMMY
|
||||
133 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:148:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
AIR301_names.py:137:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
|
|
||||
147 | # airflow.www.auth
|
||||
148 | has_access
|
||||
136 | # airflow.www.auth
|
||||
137 | has_access
|
||||
| ^^^^^^^^^^ AIR301
|
||||
149 |
|
||||
150 | # airflow.www.utils
|
||||
138 |
|
||||
139 | # airflow.www.utils
|
||||
|
|
||||
= help: Use `airflow.www.auth.has_access_*` instead
|
||||
|
||||
AIR301_names.py:151:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
AIR301_names.py:140:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
|
|
||||
150 | # airflow.www.utils
|
||||
151 | get_sensitive_variables_fields
|
||||
139 | # airflow.www.utils
|
||||
140 | get_sensitive_variables_fields
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
152 | should_hide_value_for_key
|
||||
141 | should_hide_value_for_key
|
||||
|
|
||||
= help: Use `airflow.utils.log.secrets_masker.get_sensitive_variables_fields` instead
|
||||
|
||||
AIR301_names.py:152:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
AIR301_names.py:141:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
|
|
||||
150 | # airflow.www.utils
|
||||
151 | get_sensitive_variables_fields
|
||||
152 | should_hide_value_for_key
|
||||
139 | # airflow.www.utils
|
||||
140 | get_sensitive_variables_fields
|
||||
141 | should_hide_value_for_key
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
153 |
|
||||
154 | # airflow.operators.python
|
||||
142 |
|
||||
143 | # airflow.operators.python
|
||||
|
|
||||
= help: Use `airflow.utils.log.secrets_masker.should_hide_value_for_key` instead
|
||||
|
||||
AIR301_names.py:157:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0
|
||||
AIR301_names.py:146:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0
|
||||
|
|
||||
155 | from airflow.operators.python import get_current_context
|
||||
156 |
|
||||
157 | get_current_context()
|
||||
144 | from airflow.operators.python import get_current_context
|
||||
145 |
|
||||
146 | get_current_context()
|
||||
| ^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
147 |
|
||||
148 | # airflow.providers.mysql
|
||||
|
|
||||
= help: Use `airflow.sdk.get_current_context` instead
|
||||
|
||||
AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
149 | from airflow.providers.mysql.datasets.mysql import sanitize_uri
|
||||
150 |
|
||||
151 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
152 |
|
||||
153 | # airflow.providers.postgres
|
||||
|
|
||||
= help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
154 | from airflow.providers.postgres.datasets.postgres import sanitize_uri
|
||||
155 |
|
||||
156 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
157 |
|
||||
158 | # airflow.providers.trino
|
||||
|
|
||||
= help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
159 | from airflow.providers.trino.datasets.trino import sanitize_uri
|
||||
160 |
|
||||
161 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,337 +1,394 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR311_names.py:23:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:27:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
22 | # airflow
|
||||
23 | DatasetFromRoot()
|
||||
26 | # airflow
|
||||
27 | DatasetFromRoot()
|
||||
| ^^^^^^^^^^^^^^^ AIR311
|
||||
24 |
|
||||
25 | # airflow.datasets
|
||||
28 |
|
||||
29 | # airflow.datasets
|
||||
|
|
||||
= help: Use `airflow.sdk.Asset` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import Asset
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 |-DatasetFromRoot()
|
||||
24 |+Asset()
|
||||
24 25 |
|
||||
25 26 | # airflow.datasets
|
||||
26 27 | Dataset()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import Asset
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 |-DatasetFromRoot()
|
||||
28 |+Asset()
|
||||
28 29 |
|
||||
29 30 | # airflow.datasets
|
||||
30 31 | Dataset()
|
||||
|
||||
AIR311_names.py:26:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:30:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
25 | # airflow.datasets
|
||||
26 | Dataset()
|
||||
29 | # airflow.datasets
|
||||
30 | Dataset()
|
||||
| ^^^^^^^ AIR311
|
||||
27 | DatasetAlias()
|
||||
28 | DatasetAll()
|
||||
31 | DatasetAlias()
|
||||
32 | DatasetAll()
|
||||
|
|
||||
= help: Use `airflow.sdk.Asset` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import Asset
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
24 25 |
|
||||
25 26 | # airflow.datasets
|
||||
26 |-Dataset()
|
||||
27 |+Asset()
|
||||
27 28 | DatasetAlias()
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import Asset
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
28 29 |
|
||||
29 30 | # airflow.datasets
|
||||
30 |-Dataset()
|
||||
31 |+Asset()
|
||||
31 32 | DatasetAlias()
|
||||
32 33 | DatasetAll()
|
||||
33 34 | DatasetAny()
|
||||
|
||||
AIR311_names.py:27:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
25 | # airflow.datasets
|
||||
26 | Dataset()
|
||||
27 | DatasetAlias()
|
||||
29 | # airflow.datasets
|
||||
30 | Dataset()
|
||||
31 | DatasetAlias()
|
||||
| ^^^^^^^^^^^^ AIR311
|
||||
28 | DatasetAll()
|
||||
29 | DatasetAny()
|
||||
32 | DatasetAll()
|
||||
33 | DatasetAny()
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAlias` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAlias
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
24 25 |
|
||||
25 26 | # airflow.datasets
|
||||
26 27 | Dataset()
|
||||
27 |-DatasetAlias()
|
||||
28 |+AssetAlias()
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import AssetAlias
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
28 29 |
|
||||
29 30 | # airflow.datasets
|
||||
30 31 | Dataset()
|
||||
31 |-DatasetAlias()
|
||||
32 |+AssetAlias()
|
||||
32 33 | DatasetAll()
|
||||
33 34 | DatasetAny()
|
||||
34 35 | Metadata()
|
||||
|
||||
AIR311_names.py:28:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:32:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
26 | Dataset()
|
||||
27 | DatasetAlias()
|
||||
28 | DatasetAll()
|
||||
30 | Dataset()
|
||||
31 | DatasetAlias()
|
||||
32 | DatasetAll()
|
||||
| ^^^^^^^^^^ AIR311
|
||||
29 | DatasetAny()
|
||||
30 | Metadata()
|
||||
33 | DatasetAny()
|
||||
34 | Metadata()
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAll` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAll
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import AssetAll
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | # airflow.datasets
|
||||
26 27 | Dataset()
|
||||
27 28 | DatasetAlias()
|
||||
28 |-DatasetAll()
|
||||
29 |+AssetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
31 32 | expand_alias_to_datasets()
|
||||
29 30 | # airflow.datasets
|
||||
30 31 | Dataset()
|
||||
31 32 | DatasetAlias()
|
||||
32 |-DatasetAll()
|
||||
33 |+AssetAll()
|
||||
33 34 | DatasetAny()
|
||||
34 35 | Metadata()
|
||||
35 36 | expand_alias_to_datasets()
|
||||
|
||||
AIR311_names.py:29:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:33:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
27 | DatasetAlias()
|
||||
28 | DatasetAll()
|
||||
29 | DatasetAny()
|
||||
31 | DatasetAlias()
|
||||
32 | DatasetAll()
|
||||
33 | DatasetAny()
|
||||
| ^^^^^^^^^^ AIR311
|
||||
30 | Metadata()
|
||||
31 | expand_alias_to_datasets()
|
||||
34 | Metadata()
|
||||
35 | expand_alias_to_datasets()
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAny` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAny
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import AssetAny
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | Dataset()
|
||||
27 28 | DatasetAlias()
|
||||
28 29 | DatasetAll()
|
||||
29 |-DatasetAny()
|
||||
30 |+AssetAny()
|
||||
30 31 | Metadata()
|
||||
31 32 | expand_alias_to_datasets()
|
||||
32 33 |
|
||||
30 31 | Dataset()
|
||||
31 32 | DatasetAlias()
|
||||
32 33 | DatasetAll()
|
||||
33 |-DatasetAny()
|
||||
34 |+AssetAny()
|
||||
34 35 | Metadata()
|
||||
35 36 | expand_alias_to_datasets()
|
||||
36 37 |
|
||||
|
||||
AIR311_names.py:30:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:34:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
28 | DatasetAll()
|
||||
29 | DatasetAny()
|
||||
30 | Metadata()
|
||||
32 | DatasetAll()
|
||||
33 | DatasetAny()
|
||||
34 | Metadata()
|
||||
| ^^^^^^^^ AIR311
|
||||
31 | expand_alias_to_datasets()
|
||||
35 | expand_alias_to_datasets()
|
||||
|
|
||||
= help: Use `airflow.sdk.Metadata` instead
|
||||
|
||||
AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:35:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
29 | DatasetAny()
|
||||
30 | Metadata()
|
||||
31 | expand_alias_to_datasets()
|
||||
33 | DatasetAny()
|
||||
34 | Metadata()
|
||||
35 | expand_alias_to_datasets()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ AIR311
|
||||
32 |
|
||||
33 | # airflow.decorators
|
||||
36 |
|
||||
37 | # airflow.decorators
|
||||
|
|
||||
= help: Use `airflow.sdk.expand_alias_to_assets` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import expand_alias_to_assets
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import expand_alias_to_assets
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
31 |-expand_alias_to_datasets()
|
||||
32 |+expand_alias_to_assets()
|
||||
32 33 |
|
||||
33 34 | # airflow.decorators
|
||||
34 35 | dag()
|
||||
32 33 | DatasetAll()
|
||||
33 34 | DatasetAny()
|
||||
34 35 | Metadata()
|
||||
35 |-expand_alias_to_datasets()
|
||||
36 |+expand_alias_to_assets()
|
||||
36 37 |
|
||||
37 38 | # airflow.decorators
|
||||
38 39 | dag()
|
||||
|
||||
AIR311_names.py:34:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:38:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
33 | # airflow.decorators
|
||||
34 | dag()
|
||||
37 | # airflow.decorators
|
||||
38 | dag()
|
||||
| ^^^ AIR311
|
||||
35 | task()
|
||||
36 | task_group()
|
||||
39 | task()
|
||||
40 | task_group()
|
||||
|
|
||||
= help: Use `airflow.sdk.dag` instead
|
||||
|
||||
AIR311_names.py:35:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:39:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
33 | # airflow.decorators
|
||||
34 | dag()
|
||||
35 | task()
|
||||
37 | # airflow.decorators
|
||||
38 | dag()
|
||||
39 | task()
|
||||
| ^^^^ AIR311
|
||||
36 | task_group()
|
||||
37 | setup()
|
||||
40 | task_group()
|
||||
41 | setup()
|
||||
|
|
||||
= help: Use `airflow.sdk.task` instead
|
||||
|
||||
AIR311_names.py:36:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:40:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
34 | dag()
|
||||
35 | task()
|
||||
36 | task_group()
|
||||
38 | dag()
|
||||
39 | task()
|
||||
40 | task_group()
|
||||
| ^^^^^^^^^^ AIR311
|
||||
37 | setup()
|
||||
38 | teardown()
|
||||
41 | setup()
|
||||
42 | teardown()
|
||||
|
|
||||
= help: Use `airflow.sdk.task_group` instead
|
||||
|
||||
AIR311_names.py:37:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:41:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
35 | task()
|
||||
36 | task_group()
|
||||
37 | setup()
|
||||
39 | task()
|
||||
40 | task_group()
|
||||
41 | setup()
|
||||
| ^^^^^ AIR311
|
||||
38 | teardown()
|
||||
42 | teardown()
|
||||
|
|
||||
= help: Use `airflow.sdk.setup` instead
|
||||
|
||||
AIR311_names.py:38:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:42:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
36 | task_group()
|
||||
37 | setup()
|
||||
38 | teardown()
|
||||
40 | task_group()
|
||||
41 | setup()
|
||||
42 | teardown()
|
||||
| ^^^^^^^^ AIR311
|
||||
39 |
|
||||
40 | # airflow.io
|
||||
43 |
|
||||
44 | # airflow.io
|
||||
|
|
||||
= help: Use `airflow.sdk.teardown` instead
|
||||
|
||||
AIR311_names.py:41:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:45:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
40 | # airflow.io
|
||||
41 | ObjectStoragePath()
|
||||
44 | # airflow.io
|
||||
45 | ObjectStoragePath()
|
||||
| ^^^^^^^^^^^^^^^^^ AIR311
|
||||
42 | attach()
|
||||
46 | attach()
|
||||
|
|
||||
= help: Use `airflow.sdk.ObjectStoragePath` instead
|
||||
|
||||
AIR311_names.py:42:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:46:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
40 | # airflow.io
|
||||
41 | ObjectStoragePath()
|
||||
42 | attach()
|
||||
44 | # airflow.io
|
||||
45 | ObjectStoragePath()
|
||||
46 | attach()
|
||||
| ^^^^^^ AIR311
|
||||
43 |
|
||||
44 | # airflow.models
|
||||
47 |
|
||||
48 | # airflow.models
|
||||
|
|
||||
= help: Use `airflow.sdk.io.attach` instead
|
||||
|
||||
AIR311_names.py:45:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
44 | # airflow.models
|
||||
45 | DAGFromModel()
|
||||
48 | # airflow.models
|
||||
49 | Connection()
|
||||
| ^^^^^^^^^^ AIR311
|
||||
50 | DAGFromModel()
|
||||
51 | Variable()
|
||||
|
|
||||
= help: Use `airflow.sdk.Connection` instead
|
||||
|
||||
AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
48 | # airflow.models
|
||||
49 | Connection()
|
||||
50 | DAGFromModel()
|
||||
| ^^^^^^^^^^^^ AIR311
|
||||
46 |
|
||||
47 | # airflow.models.baseoperator
|
||||
51 | Variable()
|
||||
|
|
||||
= help: Use `airflow.sdk.DAG` instead
|
||||
|
||||
AIR311_names.py:48:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
ℹ Safe fix
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import DAG
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
47 48 |
|
||||
48 49 | # airflow.models
|
||||
49 50 | Connection()
|
||||
50 |-DAGFromModel()
|
||||
51 |+DAG()
|
||||
51 52 | Variable()
|
||||
52 53 |
|
||||
53 54 | # airflow.models.baseoperator
|
||||
|
||||
AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
47 | # airflow.models.baseoperator
|
||||
48 | chain()
|
||||
49 | Connection()
|
||||
50 | DAGFromModel()
|
||||
51 | Variable()
|
||||
| ^^^^^^^^ AIR311
|
||||
52 |
|
||||
53 | # airflow.models.baseoperator
|
||||
|
|
||||
= help: Use `airflow.sdk.Variable` instead
|
||||
|
||||
AIR311_names.py:54:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
53 | # airflow.models.baseoperator
|
||||
54 | chain()
|
||||
| ^^^^^ AIR311
|
||||
49 | chain_linear()
|
||||
50 | cross_downstream()
|
||||
55 | chain_linear()
|
||||
56 | cross_downstream()
|
||||
|
|
||||
= help: Use `airflow.sdk.chain` instead
|
||||
|
||||
AIR311_names.py:49:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:55:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
47 | # airflow.models.baseoperator
|
||||
48 | chain()
|
||||
49 | chain_linear()
|
||||
53 | # airflow.models.baseoperator
|
||||
54 | chain()
|
||||
55 | chain_linear()
|
||||
| ^^^^^^^^^^^^ AIR311
|
||||
50 | cross_downstream()
|
||||
56 | cross_downstream()
|
||||
|
|
||||
= help: Use `airflow.sdk.chain_linear` instead
|
||||
|
||||
AIR311_names.py:50:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
48 | chain()
|
||||
49 | chain_linear()
|
||||
50 | cross_downstream()
|
||||
54 | chain()
|
||||
55 | chain_linear()
|
||||
56 | cross_downstream()
|
||||
| ^^^^^^^^^^^^^^^^ AIR311
|
||||
51 |
|
||||
52 | # airflow.models.baseoperatolinker
|
||||
57 |
|
||||
58 | # airflow.models.baseoperatolinker
|
||||
|
|
||||
= help: Use `airflow.sdk.cross_downstream` instead
|
||||
|
||||
AIR311_names.py:56:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
55 | # airflow.models.dag
|
||||
56 | DAGFromDag()
|
||||
61 | # airflow.models.dag
|
||||
62 | DAGFromDag()
|
||||
| ^^^^^^^^^^ AIR311
|
||||
57 | # airflow.timetables.datasets
|
||||
58 | DatasetOrTimeSchedule()
|
||||
63 | # airflow.timetables.datasets
|
||||
64 | DatasetOrTimeSchedule()
|
||||
|
|
||||
= help: Use `airflow.sdk.DAG` instead
|
||||
|
||||
AIR311_names.py:58:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
ℹ Safe fix
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.sdk import DAG
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
59 60 | BaseOperatorLink()
|
||||
60 61 |
|
||||
61 62 | # airflow.models.dag
|
||||
62 |-DAGFromDag()
|
||||
63 |+DAG()
|
||||
63 64 | # airflow.timetables.datasets
|
||||
64 65 | DatasetOrTimeSchedule()
|
||||
65 66 |
|
||||
|
||||
AIR311_names.py:64:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
56 | DAGFromDag()
|
||||
57 | # airflow.timetables.datasets
|
||||
58 | DatasetOrTimeSchedule()
|
||||
62 | DAGFromDag()
|
||||
63 | # airflow.timetables.datasets
|
||||
64 | DatasetOrTimeSchedule()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR311
|
||||
59 |
|
||||
60 | # airflow.utils.dag_parsing_context
|
||||
65 |
|
||||
66 | # airflow.utils.dag_parsing_context
|
||||
|
|
||||
= help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.timetables.assets import AssetOrTimeSchedule
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
22 22 | from airflow.models.dag import DAG as DAGFromDag
|
||||
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
25 |+from airflow.timetables.assets import AssetOrTimeSchedule
|
||||
25 26 |
|
||||
26 27 | # airflow
|
||||
27 28 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
55 56 | # airflow.models.dag
|
||||
56 57 | DAGFromDag()
|
||||
57 58 | # airflow.timetables.datasets
|
||||
58 |-DatasetOrTimeSchedule()
|
||||
59 |+AssetOrTimeSchedule()
|
||||
59 60 |
|
||||
60 61 | # airflow.utils.dag_parsing_context
|
||||
61 62 | get_parsing_context()
|
||||
61 62 | # airflow.models.dag
|
||||
62 63 | DAGFromDag()
|
||||
63 64 | # airflow.timetables.datasets
|
||||
64 |-DatasetOrTimeSchedule()
|
||||
65 |+AssetOrTimeSchedule()
|
||||
65 66 |
|
||||
66 67 | # airflow.utils.dag_parsing_context
|
||||
67 68 | get_parsing_context()
|
||||
|
||||
AIR311_names.py:61:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:67:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
60 | # airflow.utils.dag_parsing_context
|
||||
61 | get_parsing_context()
|
||||
66 | # airflow.utils.dag_parsing_context
|
||||
67 | get_parsing_context()
|
||||
| ^^^^^^^^^^^^^^^^^^^ AIR311
|
||||
|
|
||||
= help: Use `airflow.sdk.get_parsing_context` instead
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
|
||||
@@ -100,7 +100,15 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) {
|
||||
}
|
||||
|
||||
// f"select * from table where val = {val}"
|
||||
Expr::FString(f_string) => concatenated_f_string(f_string, checker.locator()),
|
||||
Expr::FString(f_string)
|
||||
if f_string
|
||||
.value
|
||||
.f_strings()
|
||||
.any(|fs| fs.elements.iter().any(ast::FStringElement::is_expression)) =>
|
||||
{
|
||||
concatenated_f_string(f_string, checker.locator())
|
||||
}
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
||||
@@ -601,4 +601,6 @@ S608.py:164:11: S608 Possible SQL injection vector through string-based query co
|
||||
167 | | FROM ({user_input}) raw
|
||||
168 | | """
|
||||
| |___^ S608
|
||||
169 |
|
||||
170 | # https://github.com/astral-sh/ruff/issues/17967
|
||||
|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{checkers::ast::Checker, fix::edits::add_argument};
|
||||
@@ -60,10 +60,18 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When prefixes are supplied, stacklevel is implicitly overridden to be `max(2, stacklevel)`.
|
||||
//
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/warnings.html#warnings.warn)
|
||||
// ```text
|
||||
// 0 1 2 3 4
|
||||
// warnings.warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=())
|
||||
// ```
|
||||
if call
|
||||
.arguments
|
||||
.find_argument_value("stacklevel", 2)
|
||||
.is_some()
|
||||
|| is_skip_file_prefixes_param_set(&call.arguments)
|
||||
|| call
|
||||
.arguments
|
||||
.args
|
||||
@@ -90,3 +98,14 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) {
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
|
||||
/// Returns `true` if `skip_file_prefixes` is set to its non-default value.
|
||||
/// The default value of `skip_file_prefixes` is an empty tuple.
|
||||
fn is_skip_file_prefixes_param_set(arguments: &ast::Arguments) -> bool {
|
||||
arguments
|
||||
.find_keyword("skip_file_prefixes")
|
||||
.is_some_and(|keyword| match &keyword.value {
|
||||
Expr::Tuple(tuple) => !tuple.elts.is_empty(),
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,3 +61,26 @@ B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
26 |- source = None # no trailing comma
|
||||
26 |+ source = None, stacklevel=2 # no trailing comma
|
||||
27 27 | )
|
||||
28 28 |
|
||||
29 29 | # https://github.com/astral-sh/ruff/issues/18011
|
||||
|
||||
B028.py:32:1: B028 [*] No explicit `stacklevel` keyword argument found
|
||||
|
|
||||
30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),))
|
||||
31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value
|
||||
32 | warnings.warn("test", skip_file_prefixes=())
|
||||
| ^^^^^^^^^^^^^ B028
|
||||
33 |
|
||||
34 | _my_prefixes = ("this","that")
|
||||
|
|
||||
= help: Set `stacklevel=2`
|
||||
|
||||
ℹ Unsafe fix
|
||||
29 29 | # https://github.com/astral-sh/ruff/issues/18011
|
||||
30 30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),))
|
||||
31 31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value
|
||||
32 |-warnings.warn("test", skip_file_prefixes=())
|
||||
32 |+warnings.warn("test", skip_file_prefixes=(), stacklevel=2)
|
||||
33 33 |
|
||||
34 34 | _my_prefixes = ("this","that")
|
||||
35 35 | warnings.warn("test", skip_file_prefixes = _my_prefixes)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
@@ -19,6 +19,7 @@ use crate::fix::edits::{remove_argument, Parentheses};
|
||||
/// arguments directly.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// def foo(bar):
|
||||
/// return bar + 1
|
||||
@@ -28,6 +29,7 @@ use crate::fix::edits::{remove_argument, Parentheses};
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// def foo(bar):
|
||||
/// return bar + 1
|
||||
@@ -36,6 +38,26 @@ use crate::fix::edits::{remove_argument, Parentheses};
|
||||
/// print(foo(bar=2)) # prints 3
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// This rule's fix is marked as unsafe for dictionaries with comments interleaved between
|
||||
/// the items, as comments may be removed.
|
||||
///
|
||||
/// For example, the fix would be marked as unsafe in the following case:
|
||||
///
|
||||
/// ```python
|
||||
/// foo(
|
||||
/// **{
|
||||
/// # comment
|
||||
/// "x": 1.0,
|
||||
/// # comment
|
||||
/// "y": 2.0,
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// as this is converted to `foo(x=1.0, y=2.0)` without any of the comments.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Dictionary displays](https://docs.python.org/3/reference/expressions.html#dictionary-displays)
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
@@ -113,7 +135,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
|
||||
.iter()
|
||||
.all(|kwarg| !duplicate_keywords.contains(kwarg))
|
||||
{
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
let edit = Edit::range_replacement(
|
||||
kwargs
|
||||
.iter()
|
||||
.zip(dict.iter_values())
|
||||
@@ -134,7 +156,15 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
|
||||
})
|
||||
.join(", "),
|
||||
keyword.range(),
|
||||
)));
|
||||
);
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
edit,
|
||||
if checker.comment_ranges().intersects(dict.range()) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,8 @@ PIE804.py:29:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
29 |-abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
29 |+abc(foo=1, bar=(bar := 1)) # PIE804
|
||||
30 30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
31 31 |
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
|
||||
PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
@@ -215,6 +217,8 @@ PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PIE804
|
||||
31 |
|
||||
32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
@@ -224,3 +228,34 @@ PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
29 29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
30 |-abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
30 |+abc(foo=1, bar=(yield 1)) # PIE804
|
||||
31 31 |
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
33 33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
|
||||
PIE804.py:35:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
34 | foo(
|
||||
35 | / **{
|
||||
36 | | # Comment 1
|
||||
37 | | "x": 1.0,
|
||||
38 | | # Comment 2
|
||||
39 | | "y": 2.0,
|
||||
40 | | }
|
||||
| |_____^ PIE804
|
||||
41 | )
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
ℹ Unsafe fix
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
33 33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
34 34 | foo(
|
||||
35 |- **{
|
||||
36 |- # Comment 1
|
||||
37 |- "x": 1.0,
|
||||
38 |- # Comment 2
|
||||
39 |- "y": 2.0,
|
||||
40 |- }
|
||||
35 |+ x=1.0, y=2.0
|
||||
41 36 | )
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::name::UnqualifiedName;
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
@@ -11,6 +12,7 @@ use ruff_python_semantic::SemanticModel;
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits;
|
||||
@@ -807,10 +809,51 @@ fn check_fixture_returns(checker: &Checker, name: &str, body: &[Stmt], returns:
|
||||
}
|
||||
|
||||
/// PT019
|
||||
fn check_test_function_args(checker: &Checker, parameters: &Parameters) {
|
||||
fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorators: &[Decorator]) {
|
||||
let mut named_parametrize = FxHashSet::default();
|
||||
for decorator in decorators.iter().filter(|decorator| {
|
||||
UnqualifiedName::from_expr(map_callable(&decorator.expression))
|
||||
.is_some_and(|name| matches!(name.segments(), ["pytest", "mark", "parametrize"]))
|
||||
}) {
|
||||
let Some(call_expr) = decorator.expression.as_call_expr() else {
|
||||
continue;
|
||||
};
|
||||
let Some(first_arg) = call_expr.arguments.find_argument_value("argnames", 0) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match first_arg {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
named_parametrize.extend(
|
||||
value
|
||||
.to_str()
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|param| !param.is_empty() && param.starts_with('_')),
|
||||
);
|
||||
}
|
||||
|
||||
Expr::Name(_) => return,
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
if elts.iter().any(Expr::is_name_expr) =>
|
||||
{
|
||||
return
|
||||
}
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
named_parametrize.extend(
|
||||
elts.iter()
|
||||
.filter_map(Expr::as_string_literal_expr)
|
||||
.map(|param| param.value.to_str().trim())
|
||||
.filter(|param| !param.is_empty() && param.starts_with('_')),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for parameter in parameters.iter_non_variadic_params() {
|
||||
let name = parameter.name();
|
||||
if name.starts_with('_') {
|
||||
if name.starts_with('_') && !named_parametrize.contains(name.as_str()) {
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
PytestFixtureParamWithoutValue {
|
||||
name: name.to_string(),
|
||||
@@ -915,6 +958,6 @@ pub(crate) fn fixture(
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::PytestFixtureParamWithoutValue) && name.starts_with("test_") {
|
||||
check_test_function_args(checker, parameters);
|
||||
check_test_function_args(checker, parameters, decorators);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PT019.py:9:14: PT019 Fixture `_fixture` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
@@ -15,3 +14,19 @@ PT019.py:13:17: PT019 Fixture `_fixture` without value is injected as parameter,
|
||||
| ^^^^^^^^ PT019
|
||||
14 | pass
|
||||
|
|
||||
|
||||
PT019.py:31:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
30 | @pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
31 | def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
| ^^^^ PT019
|
||||
32 | pass
|
||||
|
|
||||
|
||||
PT019.py:39:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
38 | @pytest.mark.parametrize(("_foo"), [1, 2, 3])
|
||||
39 | def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
| ^^^^ PT019
|
||||
40 | pass
|
||||
|
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn split_static_string(
|
||||
let sep_arg = arguments.find_argument_value("sep", 0);
|
||||
let split_replacement = if let Some(sep) = sep_arg {
|
||||
match sep {
|
||||
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value),
|
||||
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction),
|
||||
Expr::StringLiteral(sep_value) => {
|
||||
let sep_value_str = sep_value.value.to_str();
|
||||
Some(split_sep(
|
||||
@@ -99,7 +99,7 @@ pub(crate) fn split_static_string(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
split_default(str_value, maxsplit_value)
|
||||
split_default(str_value, maxsplit_value, direction)
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(SplitStaticString, call.range());
|
||||
@@ -144,7 +144,11 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
|
||||
})
|
||||
}
|
||||
|
||||
fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr> {
|
||||
fn split_default(
|
||||
str_value: &StringLiteralValue,
|
||||
max_split: i32,
|
||||
direction: Direction,
|
||||
) -> Option<Expr> {
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
@@ -152,6 +156,7 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr>
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
let string_val = str_value.to_str();
|
||||
match max_split.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
// Autofix for `maxsplit` without separator not yet implemented, as
|
||||
@@ -160,14 +165,30 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr>
|
||||
None
|
||||
}
|
||||
Ordering::Equal => {
|
||||
let list_items: Vec<&str> = vec![str_value.to_str()];
|
||||
// Behavior for maxsplit = 0 when sep is None:
|
||||
// - If the string is empty or all whitespace, result is [].
|
||||
// - Otherwise:
|
||||
// - " x ".split(maxsplit=0) -> ['x ']
|
||||
// - " x ".rsplit(maxsplit=0) -> [' x']
|
||||
// - "".split(maxsplit=0) -> []
|
||||
// - " ".split(maxsplit=0) -> []
|
||||
let processed_str = if direction == Direction::Left {
|
||||
string_val.trim_start()
|
||||
} else {
|
||||
string_val.trim_end()
|
||||
};
|
||||
let list_items: &[_] = if processed_str.is_empty() {
|
||||
&[]
|
||||
} else {
|
||||
&[processed_str]
|
||||
};
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Less => {
|
||||
let list_items: Vec<&str> = str_value.to_str().split_whitespace().collect();
|
||||
let list_items: Vec<&str> = string_val.split_whitespace().collect();
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
str_value.first_literal_flags(),
|
||||
@@ -186,12 +207,20 @@ fn split_sep(
|
||||
let list_items: Vec<&str> = if let Ok(split_n) = usize::try_from(max_split) {
|
||||
match direction {
|
||||
Direction::Left => value.splitn(split_n + 1, sep_value).collect(),
|
||||
Direction::Right => value.rsplitn(split_n + 1, sep_value).collect(),
|
||||
Direction::Right => {
|
||||
let mut items: Vec<&str> = value.rsplitn(split_n + 1, sep_value).collect();
|
||||
items.reverse();
|
||||
items
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match direction {
|
||||
Direction::Left => value.split(sep_value).collect(),
|
||||
Direction::Right => value.rsplit(sep_value).collect(),
|
||||
Direction::Right => {
|
||||
let mut items: Vec<&str> = value.rsplit(sep_value).collect();
|
||||
items.reverse();
|
||||
items
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -352,7 +352,7 @@ SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
33 | "".split() # []
|
||||
34 | """
|
||||
34 | """
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
@@ -363,7 +363,7 @@ SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
32 |-" a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
32 |+[" a", "a a", "a a "] # [" a", "a a", "a a "]
|
||||
33 33 | "".split() # []
|
||||
34 34 | """
|
||||
34 34 | """
|
||||
35 35 | """.split() # []
|
||||
|
||||
SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
@@ -371,7 +371,7 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
33 | "".split() # []
|
||||
| ^^^^^^^^^^ SIM905
|
||||
34 | """
|
||||
34 | """
|
||||
35 | """.split() # []
|
||||
|
|
||||
= help: Replace with list literal
|
||||
@@ -382,7 +382,7 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
33 |-"".split() # []
|
||||
33 |+[] # []
|
||||
34 34 | """
|
||||
34 34 | """
|
||||
35 35 | """.split() # []
|
||||
36 36 | " ".split() # []
|
||||
|
||||
@@ -390,7 +390,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
33 | "".split() # []
|
||||
34 | / """
|
||||
34 | / """
|
||||
35 | | """.split() # []
|
||||
| |___________^ SIM905
|
||||
36 | " ".split() # []
|
||||
@@ -402,7 +402,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
31 31 |
|
||||
32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
|
||||
33 33 | "".split() # []
|
||||
34 |-"""
|
||||
34 |-"""
|
||||
35 |-""".split() # []
|
||||
34 |+[] # []
|
||||
36 35 | " ".split() # []
|
||||
@@ -411,7 +411,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
||||
SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
34 | """
|
||||
34 | """
|
||||
35 | """.split() # []
|
||||
36 | " ".split() # []
|
||||
| ^^^^^^^^^^^^^^^^^ SIM905
|
||||
@@ -422,7 +422,7 @@ SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | "".split() # []
|
||||
34 34 | """
|
||||
34 34 | """
|
||||
35 35 | """.split() # []
|
||||
36 |-" ".split() # []
|
||||
36 |+[] # []
|
||||
@@ -442,7 +442,7 @@ SIM905.py:37:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
34 34 | """
|
||||
34 34 | """
|
||||
35 35 | """.split() # []
|
||||
36 36 | " ".split() # []
|
||||
37 |-"/abc/".split() # ["/abc/"]
|
||||
@@ -854,6 +854,8 @@ SIM905.py:103:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
107 | | "'itemD'"
|
||||
108 | | """.split()
|
||||
| |___________^ SIM905
|
||||
109 |
|
||||
110 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
@@ -868,3 +870,393 @@ SIM905.py:103:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
107 |-"'itemD'"
|
||||
108 |-""".split()
|
||||
103 |+['"itemA"', "'itemB'", "'''itemC'''", "\"'itemD'\""]
|
||||
109 104 |
|
||||
110 105 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
111 106 | print("a,b".rsplit(","))
|
||||
|
||||
SIM905.py:111:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
110 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
111 | print("a,b".rsplit(","))
|
||||
| ^^^^^^^^^^^^^^^^^ SIM905
|
||||
112 | print("a,b,c".rsplit(",", 1))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
108 108 | """.split()
|
||||
109 109 |
|
||||
110 110 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
111 |-print("a,b".rsplit(","))
|
||||
111 |+print(["a", "b"])
|
||||
112 112 | print("a,b,c".rsplit(",", 1))
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
|
||||
SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
110 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
111 | print("a,b".rsplit(","))
|
||||
112 | print("a,b,c".rsplit(",", 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
113 |
|
||||
114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
109 109 |
|
||||
110 110 | # https://github.com/astral-sh/ruff/issues/18042
|
||||
111 111 | print("a,b".rsplit(","))
|
||||
112 |-print("a,b,c".rsplit(",", 1))
|
||||
112 |+print(["a,b", "c"])
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
|
||||
SIM905.py:116:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 |
|
||||
116 | print("".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
116 |-print("".split(maxsplit=0))
|
||||
116 |+print([])
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:117:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
116 | print("".split(maxsplit=0))
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 |-print("".split(sep=None, maxsplit=0))
|
||||
117 |+print([])
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
|
||||
SIM905.py:118:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
116 | print("".split(maxsplit=0))
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
115 115 |
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 |-print(" ".split(maxsplit=0))
|
||||
118 |+print([])
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:119:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 |-print(" ".split(sep=None, maxsplit=0))
|
||||
119 |+print([])
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
|
||||
SIM905.py:120:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 |-print(" x ".split(maxsplit=0))
|
||||
120 |+print(["x "])
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:121:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 |-print(" x ".split(sep=None, maxsplit=0))
|
||||
121 |+print(["x "])
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:122:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 |-print(" x ".split(maxsplit=0))
|
||||
122 |+print(["x "])
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:123:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 |-print(" x ".split(sep=None, maxsplit=0))
|
||||
123 |+print(["x "])
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:124:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 |-print("".rsplit(maxsplit=0))
|
||||
124 |+print([])
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:125:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 |-print("".rsplit(sep=None, maxsplit=0))
|
||||
125 |+print([])
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:126:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 |-print(" ".rsplit(maxsplit=0))
|
||||
126 |+print([])
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:127:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 |-print(" ".rsplit(sep=None, maxsplit=0))
|
||||
127 |+print([])
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:128:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 |-print(" x ".rsplit(maxsplit=0))
|
||||
128 |+print([" x"])
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:129:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 |-print(" x ".rsplit(maxsplit=0))
|
||||
129 |+print([" x"])
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:130:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 |-print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
130 |+print([" x"])
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:131:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 |-print(" x ".rsplit(maxsplit=0))
|
||||
131 |+print([" x"])
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:132:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 |-print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
132 |+print([" x"])
|
||||
|
||||
@@ -26,41 +26,109 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
// PTH100
|
||||
["os", "path", "abspath"] => OsPathAbspath.into(),
|
||||
// PTH101
|
||||
["os", "chmod"] => OsChmod.into(),
|
||||
["os", "chmod"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.chmod)
|
||||
// ```text
|
||||
// 0 1 2 3
|
||||
// os.chmod(path, mode, *, dir_fd=None, follow_symlinks=True)
|
||||
// ```
|
||||
if call
|
||||
.arguments
|
||||
.find_argument_value("path", 0)
|
||||
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
|| is_argument_non_default(&call.arguments, "dir_fd", 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OsChmod.into()
|
||||
}
|
||||
// PTH102
|
||||
["os", "makedirs"] => OsMakedirs.into(),
|
||||
// PTH103
|
||||
["os", "mkdir"] => OsMkdir.into(),
|
||||
["os", "mkdir"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
|
||||
// ```text
|
||||
// 0 1 2
|
||||
// os.mkdir(path, mode=0o777, *, dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 2) {
|
||||
return;
|
||||
}
|
||||
OsMkdir.into()
|
||||
}
|
||||
// PTH104
|
||||
["os", "rename"] => {
|
||||
// `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are
|
||||
// are set to non-default values.
|
||||
// set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rename)
|
||||
// ```text
|
||||
// 0 1 2 3
|
||||
// os.rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
|
||||
// ```
|
||||
if call
|
||||
.arguments
|
||||
.find_argument_value("src_dir_fd", 2)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
|| call
|
||||
.arguments
|
||||
.find_argument_value("dst_dir_fd", 3)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
if is_argument_non_default(&call.arguments, "src_dir_fd", 2)
|
||||
|| is_argument_non_default(&call.arguments, "dst_dir_fd", 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OsRename.into()
|
||||
}
|
||||
// PTH105
|
||||
["os", "replace"] => OsReplace.into(),
|
||||
["os", "replace"] => {
|
||||
// `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are
|
||||
// set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.replace)
|
||||
// ```text
|
||||
// 0 1 2 3
|
||||
// os.replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "src_dir_fd", 2)
|
||||
|| is_argument_non_default(&call.arguments, "dst_dir_fd", 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OsReplace.into()
|
||||
}
|
||||
// PTH106
|
||||
["os", "rmdir"] => OsRmdir.into(),
|
||||
["os", "rmdir"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.rmdir(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 1) {
|
||||
return;
|
||||
}
|
||||
OsRmdir.into()
|
||||
}
|
||||
// PTH107
|
||||
["os", "remove"] => OsRemove.into(),
|
||||
["os", "remove"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.remove(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 1) {
|
||||
return;
|
||||
}
|
||||
OsRemove.into()
|
||||
}
|
||||
// PTH108
|
||||
["os", "unlink"] => OsUnlink.into(),
|
||||
["os", "unlink"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.unlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 1) {
|
||||
return;
|
||||
}
|
||||
OsUnlink.into()
|
||||
}
|
||||
// PTH109
|
||||
["os", "getcwd"] => OsGetcwd.into(),
|
||||
["os", "getcwdb"] => OsGetcwd.into(),
|
||||
@@ -76,10 +144,17 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
["os", "path", "islink"] => OsPathIslink.into(),
|
||||
// PTH116
|
||||
["os", "stat"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.stat)
|
||||
// ```text
|
||||
// 0 1 2
|
||||
// os.stat(path, *, dir_fd=None, follow_symlinks=True)
|
||||
// ```
|
||||
if call
|
||||
.arguments
|
||||
.find_positional(0)
|
||||
.find_argument_value("path", 0)
|
||||
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
|| is_argument_non_default(&call.arguments, "dir_fd", 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -148,13 +223,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. })
|
||||
)
|
||||
})
|
||||
|| is_argument_non_default(&call.arguments, "opener", 7)
|
||||
|| call
|
||||
.arguments
|
||||
.find_argument_value("opener", 7)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
|| call
|
||||
.arguments
|
||||
.find_positional(0)
|
||||
.find_argument_value("file", 0)
|
||||
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
@@ -164,17 +236,53 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
// PTH124
|
||||
["py", "path", "local"] => PyPath.into(),
|
||||
// PTH207
|
||||
["glob", "glob"] => Glob {
|
||||
function: "glob".to_string(),
|
||||
["glob", "glob"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.glob)
|
||||
// ```text
|
||||
// 0 1 2 3 4
|
||||
// glob.glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Glob {
|
||||
function: "glob".to_string(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into(),
|
||||
["glob", "iglob"] => Glob {
|
||||
function: "iglob".to_string(),
|
||||
|
||||
["glob", "iglob"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.iglob)
|
||||
// ```text
|
||||
// 0 1 2 3 4
|
||||
// glob.iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Glob {
|
||||
function: "iglob".to_string(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into(),
|
||||
// PTH115
|
||||
// Python 3.9+
|
||||
["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => OsReadlink.into(),
|
||||
["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.readlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_argument_non_default(&call.arguments, "dir_fd", 1) {
|
||||
return;
|
||||
}
|
||||
OsReadlink.into()
|
||||
}
|
||||
// PTH208
|
||||
["os", "listdir"] => {
|
||||
if call
|
||||
@@ -224,3 +332,10 @@ fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if argument `name` is set to a non-default `None` value.
|
||||
fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usize) -> bool {
|
||||
arguments
|
||||
.find_argument_value(name, position)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PTH207.py:9:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
||||
|
|
||||
@@ -26,4 +25,6 @@ PTH207.py:11:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
||||
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
|
||||
11 | search("*.png")
|
||||
| ^^^^^^ PTH207
|
||||
12 |
|
||||
13 | # if `dir_fd` is set, suppress the diagnostic
|
||||
|
|
||||
|
||||
@@ -93,7 +93,7 @@ PERF401.py:142:9: PERF401 Use a list comprehension to create a transformed list
|
||||
|
||||
PERF401.py:149:9: PERF401 Use a list comprehension to create a transformed list
|
||||
|
|
||||
147 | tmp = 1; result = [] # commment should be protected
|
||||
147 | tmp = 1; result = [] # comment should be protected
|
||||
148 | for i in range(10):
|
||||
149 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
@@ -102,7 +102,7 @@ PERF401.py:149:9: PERF401 Use a list comprehension to create a transformed list
|
||||
|
||||
PERF401.py:156:9: PERF401 Use a list comprehension to create a transformed list
|
||||
|
|
||||
154 | result = []; tmp = 1 # commment should be protected
|
||||
154 | result = []; tmp = 1 # comment should be protected
|
||||
155 | for i in range(10):
|
||||
156 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
|
||||
@@ -223,7 +223,7 @@ PERF401.py:142:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
|
||||
PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed list
|
||||
|
|
||||
147 | tmp = 1; result = [] # commment should be protected
|
||||
147 | tmp = 1; result = [] # comment should be protected
|
||||
148 | for i in range(10):
|
||||
149 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
@@ -234,10 +234,10 @@ PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
144 144 |
|
||||
145 145 | def f():
|
||||
146 146 | # make sure that `tmp` is not deleted
|
||||
147 |- tmp = 1; result = [] # commment should be protected
|
||||
147 |- tmp = 1; result = [] # comment should be protected
|
||||
148 |- for i in range(10):
|
||||
149 |- result.append(i + 1) # PERF401
|
||||
147 |+ tmp = 1 # commment should be protected
|
||||
147 |+ tmp = 1 # comment should be protected
|
||||
148 |+ result = [i + 1 for i in range(10)] # PERF401
|
||||
150 149 |
|
||||
151 150 |
|
||||
@@ -245,7 +245,7 @@ PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
|
||||
PERF401.py:156:9: PERF401 [*] Use a list comprehension to create a transformed list
|
||||
|
|
||||
154 | result = []; tmp = 1 # commment should be protected
|
||||
154 | result = []; tmp = 1 # comment should be protected
|
||||
155 | for i in range(10):
|
||||
156 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
@@ -256,10 +256,10 @@ PERF401.py:156:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
151 151 |
|
||||
152 152 | def f():
|
||||
153 153 | # make sure that `tmp` is not deleted
|
||||
154 |- result = []; tmp = 1 # commment should be protected
|
||||
154 |- result = []; tmp = 1 # comment should be protected
|
||||
155 |- for i in range(10):
|
||||
156 |- result.append(i + 1) # PERF401
|
||||
154 |+ tmp = 1 # commment should be protected
|
||||
154 |+ tmp = 1 # comment should be protected
|
||||
155 |+ result = [i + 1 for i in range(10)] # PERF401
|
||||
157 156 |
|
||||
158 157 |
|
||||
|
||||
@@ -38,6 +38,11 @@ use crate::checkers::ast::Checker;
|
||||
/// nums.add(num + 5)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This fix is always unsafe because it changes the program’s behavior. Replacing the
|
||||
/// original set with a copy during iteration allows code that would previously raise a
|
||||
/// `RuntimeError` to run without error.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `set`](https://docs.python.org/3/library/stdtypes.html#set)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -21,6 +21,7 @@ pub(crate) enum MinMax {
|
||||
/// readability.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// minimum = min(1, 2, min(3, 4, 5))
|
||||
/// maximum = max(1, 2, max(3, 4, 5))
|
||||
@@ -28,12 +29,26 @@ pub(crate) enum MinMax {
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// minimum = min(1, 2, 3, 4, 5)
|
||||
/// maximum = max(1, 2, 3, 4, 5)
|
||||
/// diff = maximum - minimum
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// This fix is always unsafe and may change the program's behavior for types without full
|
||||
/// equivalence relations, such as float comparisons involving `NaN`.
|
||||
///
|
||||
/// ```python
|
||||
/// print(min(2.0, min(float("nan"), 1.0))) # before fix: 2.0
|
||||
/// print(min(2.0, float("nan"), 1.0)) # after fix: 1.0
|
||||
///
|
||||
/// print(max(1.0, max(float("nan"), 2.0))) # before fix: 1.0
|
||||
/// print(max(1.0, float("nan"), 2.0)) # after fix: 2.0
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `min`](https://docs.python.org/3/library/functions.html#min)
|
||||
/// - [Python documentation: `max`](https://docs.python.org/3/library/functions.html#max)
|
||||
|
||||
@@ -28,6 +28,17 @@ use crate::fix::edits::add_argument;
|
||||
/// Python 3.10 and later, or `locale.getpreferredencoding()` on earlier versions,
|
||||
/// to make the encoding explicit.
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This fix is always unsafe and may change the program's behavior. It forces
|
||||
/// `encoding="utf-8"` as the default, regardless of the platform’s actual default
|
||||
/// encoding, which may cause `UnicodeDecodeError` on non-UTF-8 systems.
|
||||
/// ```python
|
||||
/// with open("test.txt") as f:
|
||||
/// print(f.read()) # before fix (on UTF-8 systems): 你好,世界!
|
||||
/// with open("test.txt", encoding="utf-8") as f:
|
||||
/// print(f.read()) # after fix (on Windows): UnicodeDecodeError
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// open("file.txt")
|
||||
|
||||
@@ -12,6 +12,11 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Why is this bad?
|
||||
/// The import alias is redundant and should be removed to avoid confusion.
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This fix is marked as always unsafe because the user may be intentionally
|
||||
/// re-exporting the import. While statements like `import numpy as numpy`
|
||||
/// appear redundant, they can have semantic meaning in certain contexts.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import numpy as numpy
|
||||
|
||||
@@ -64,7 +64,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
[
|
||||
"" | "builtins",
|
||||
"EnvironmentError" | "IOError" | "WindowsError"
|
||||
] | ["mmap" | "select" | "socket" | "os", "error"]
|
||||
] | ["mmap" | "resource" | "select" | "socket" | "os", "error"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
8 | # Testing the modules
|
||||
9 | import socket, mmap, select
|
||||
9 | import socket, mmap, select, resource
|
||||
10 | raise socket.error
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
11 | raise mmap.error
|
||||
@@ -15,32 +15,33 @@ UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
ℹ Safe fix
|
||||
7 7 |
|
||||
8 8 | # Testing the modules
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 |-raise socket.error
|
||||
10 |+raise OSError
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
13 13 | raise resource.error
|
||||
|
||||
UP024_2.py:11:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
9 | import socket, mmap, select
|
||||
9 | import socket, mmap, select, resource
|
||||
10 | raise socket.error
|
||||
11 | raise mmap.error
|
||||
| ^^^^^^^^^^ UP024
|
||||
12 | raise select.error
|
||||
13 | raise resource.error
|
||||
|
|
||||
= help: Replace `mmap.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | # Testing the modules
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 10 | raise socket.error
|
||||
11 |-raise mmap.error
|
||||
11 |+raise OSError
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
|
||||
UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
@@ -48,355 +49,416 @@ UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
11 | raise mmap.error
|
||||
12 | raise select.error
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
13 |
|
||||
14 | raise socket.error()
|
||||
13 | raise resource.error
|
||||
|
|
||||
= help: Replace `select.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 10 | raise socket.error
|
||||
11 11 | raise mmap.error
|
||||
12 |-raise select.error
|
||||
12 |+raise OSError
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 15 | raise mmap.error(1)
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
|
||||
UP024_2.py:14:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:13:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
11 | raise mmap.error
|
||||
12 | raise select.error
|
||||
13 |
|
||||
14 | raise socket.error()
|
||||
13 | raise resource.error
|
||||
| ^^^^^^^^^^^^^^ UP024
|
||||
14 |
|
||||
15 | raise socket.error()
|
||||
|
|
||||
= help: Replace `resource.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | raise socket.error
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 |-raise resource.error
|
||||
13 |+raise OSError
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
|
||||
UP024_2.py:15:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
13 | raise resource.error
|
||||
14 |
|
||||
15 | raise socket.error()
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
15 | raise mmap.error(1)
|
||||
16 | raise select.error(1, 2)
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
|
|
||||
= help: Replace `socket.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 |-raise socket.error()
|
||||
14 |+raise OSError()
|
||||
15 15 | raise mmap.error(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 |-raise socket.error()
|
||||
15 |+raise OSError()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
|
||||
UP024_2.py:15:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:16:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
14 | raise socket.error()
|
||||
15 | raise mmap.error(1)
|
||||
15 | raise socket.error()
|
||||
16 | raise mmap.error(1)
|
||||
| ^^^^^^^^^^ UP024
|
||||
16 | raise select.error(1, 2)
|
||||
17 | raise select.error(1, 2)
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
|
|
||||
= help: Replace `mmap.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 |-raise mmap.error(1)
|
||||
15 |+raise OSError(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
18 18 | raise socket.error(
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 |-raise mmap.error(1)
|
||||
16 |+raise OSError(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
|
||||
UP024_2.py:16:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:17:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
14 | raise socket.error()
|
||||
15 | raise mmap.error(1)
|
||||
16 | raise select.error(1, 2)
|
||||
15 | raise socket.error()
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
17 |
|
||||
18 | raise socket.error(
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
|
|
||||
= help: Replace `select.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 15 | raise mmap.error(1)
|
||||
16 |-raise select.error(1, 2)
|
||||
16 |+raise OSError(1, 2)
|
||||
17 17 |
|
||||
18 18 | raise socket.error(
|
||||
19 19 | 1,
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 |-raise select.error(1, 2)
|
||||
17 |+raise OSError(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 20 | raise socket.error(
|
||||
|
||||
UP024_2.py:18:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
16 | raise select.error(1, 2)
|
||||
17 |
|
||||
18 | raise socket.error(
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
| ^^^^^^^^^^^^^^ UP024
|
||||
19 |
|
||||
20 | raise socket.error(
|
||||
|
|
||||
= help: Replace `resource.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 |-raise resource.error(1, "strerror", "filename")
|
||||
18 |+raise OSError(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 20 | raise socket.error(
|
||||
21 21 | 1,
|
||||
|
||||
UP024_2.py:20:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
19 |
|
||||
20 | raise socket.error(
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
19 | 1,
|
||||
20 | 2,
|
||||
21 | 1,
|
||||
22 | 2,
|
||||
|
|
||||
= help: Replace `socket.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | raise mmap.error(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
18 |-raise socket.error(
|
||||
18 |+raise OSError(
|
||||
19 19 | 1,
|
||||
20 20 | 2,
|
||||
21 21 | 3,
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 |-raise socket.error(
|
||||
20 |+raise OSError(
|
||||
21 21 | 1,
|
||||
22 22 | 2,
|
||||
23 23 | 3,
|
||||
|
||||
UP024_2.py:25:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:27:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
24 | from mmap import error
|
||||
25 | raise error
|
||||
26 | from mmap import error
|
||||
27 | raise error
|
||||
| ^^^^^ UP024
|
||||
26 |
|
||||
27 | from socket import error
|
||||
28 |
|
||||
29 | from socket import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 | )
|
||||
23 23 |
|
||||
24 24 | from mmap import error
|
||||
25 |-raise error
|
||||
25 |+raise OSError
|
||||
26 26 |
|
||||
27 27 | from socket import error
|
||||
28 28 | raise error(1)
|
||||
24 24 | )
|
||||
25 25 |
|
||||
26 26 | from mmap import error
|
||||
27 |-raise error
|
||||
27 |+raise OSError
|
||||
28 28 |
|
||||
29 29 | from socket import error
|
||||
30 30 | raise error(1)
|
||||
|
||||
UP024_2.py:28:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:30:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
27 | from socket import error
|
||||
28 | raise error(1)
|
||||
29 | from socket import error
|
||||
30 | raise error(1)
|
||||
| ^^^^^ UP024
|
||||
29 |
|
||||
30 | from select import error
|
||||
31 |
|
||||
32 | from select import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
25 25 | raise error
|
||||
26 26 |
|
||||
27 27 | from socket import error
|
||||
28 |-raise error(1)
|
||||
28 |+raise OSError(1)
|
||||
29 29 |
|
||||
30 30 | from select import error
|
||||
31 31 | raise error(1, 2)
|
||||
27 27 | raise error
|
||||
28 28 |
|
||||
29 29 | from socket import error
|
||||
30 |-raise error(1)
|
||||
30 |+raise OSError(1)
|
||||
31 31 |
|
||||
32 32 | from select import error
|
||||
33 33 | raise error(1, 2)
|
||||
|
||||
UP024_2.py:31:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:33:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
30 | from select import error
|
||||
31 | raise error(1, 2)
|
||||
32 | from select import error
|
||||
33 | raise error(1, 2)
|
||||
| ^^^^^ UP024
|
||||
32 |
|
||||
33 | # Testing the names
|
||||
34 |
|
||||
35 | from resource import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
28 28 | raise error(1)
|
||||
29 29 |
|
||||
30 30 | from select import error
|
||||
31 |-raise error(1, 2)
|
||||
31 |+raise OSError(1, 2)
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
|
||||
UP024_2.py:34:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
33 | # Testing the names
|
||||
34 | raise EnvironmentError
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
35 | raise IOError
|
||||
36 | raise WindowsError
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
31 31 | raise error(1, 2)
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 |-raise EnvironmentError
|
||||
34 |+raise OSError
|
||||
35 35 | raise IOError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
|
||||
UP024_2.py:35:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
33 | # Testing the names
|
||||
34 | raise EnvironmentError
|
||||
35 | raise IOError
|
||||
| ^^^^^^^ UP024
|
||||
36 | raise WindowsError
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
35 |-raise IOError
|
||||
35 |+raise OSError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
30 30 | raise error(1)
|
||||
31 31 |
|
||||
32 32 | from select import error
|
||||
33 |-raise error(1, 2)
|
||||
33 |+raise OSError(1, 2)
|
||||
34 34 |
|
||||
35 35 | from resource import error
|
||||
36 36 | raise error(1, "strerror", "filename")
|
||||
|
||||
UP024_2.py:36:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
34 | raise EnvironmentError
|
||||
35 | raise IOError
|
||||
36 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
35 | from resource import error
|
||||
36 | raise error(1, "strerror", "filename")
|
||||
| ^^^^^ UP024
|
||||
37 |
|
||||
38 | raise EnvironmentError()
|
||||
38 | # Testing the names
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
35 35 | raise IOError
|
||||
36 |-raise WindowsError
|
||||
36 |+raise OSError
|
||||
33 33 | raise error(1, 2)
|
||||
34 34 |
|
||||
35 35 | from resource import error
|
||||
36 |-raise error(1, "strerror", "filename")
|
||||
36 |+raise OSError(1, "strerror", "filename")
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 39 | raise IOError(1)
|
||||
|
||||
UP024_2.py:38:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
36 | raise WindowsError
|
||||
37 |
|
||||
38 | raise EnvironmentError()
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
39 | raise IOError(1)
|
||||
40 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
35 35 | raise IOError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
38 |-raise EnvironmentError()
|
||||
38 |+raise OSError()
|
||||
39 39 | raise IOError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
|
||||
UP024_2.py:39:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
38 | raise EnvironmentError()
|
||||
39 | raise IOError(1)
|
||||
| ^^^^^^^ UP024
|
||||
40 | raise WindowsError(1, 2)
|
||||
38 | # Testing the names
|
||||
39 | raise EnvironmentError
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
40 | raise IOError
|
||||
41 | raise WindowsError
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
36 36 | raise WindowsError
|
||||
36 36 | raise error(1, "strerror", "filename")
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 |-raise IOError(1)
|
||||
39 |+raise OSError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
42 42 | raise EnvironmentError(
|
||||
38 38 | # Testing the names
|
||||
39 |-raise EnvironmentError
|
||||
39 |+raise OSError
|
||||
40 40 | raise IOError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
|
||||
UP024_2.py:40:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
38 | raise EnvironmentError()
|
||||
39 | raise IOError(1)
|
||||
40 | raise WindowsError(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
41 |
|
||||
42 | raise EnvironmentError(
|
||||
38 | # Testing the names
|
||||
39 | raise EnvironmentError
|
||||
40 | raise IOError
|
||||
| ^^^^^^^ UP024
|
||||
41 | raise WindowsError
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 39 | raise IOError(1)
|
||||
40 |-raise WindowsError(1, 2)
|
||||
40 |+raise OSError(1, 2)
|
||||
41 41 |
|
||||
42 42 | raise EnvironmentError(
|
||||
43 43 | 1,
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
40 |-raise IOError
|
||||
40 |+raise OSError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
|
||||
UP024_2.py:42:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:41:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
40 | raise WindowsError(1, 2)
|
||||
41 |
|
||||
42 | raise EnvironmentError(
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
43 | 1,
|
||||
44 | 2,
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
39 39 | raise IOError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
42 |-raise EnvironmentError(
|
||||
42 |+raise OSError(
|
||||
43 43 | 1,
|
||||
44 44 | 2,
|
||||
45 45 | 3,
|
||||
|
||||
UP024_2.py:48:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
46 | )
|
||||
47 |
|
||||
48 | raise WindowsError
|
||||
39 | raise EnvironmentError
|
||||
40 | raise IOError
|
||||
41 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
49 | raise EnvironmentError(1)
|
||||
50 | raise IOError(1, 2)
|
||||
42 |
|
||||
43 | raise EnvironmentError()
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 | 3,
|
||||
46 46 | )
|
||||
47 47 |
|
||||
48 |-raise WindowsError
|
||||
48 |+raise OSError
|
||||
49 49 | raise EnvironmentError(1)
|
||||
50 50 | raise IOError(1, 2)
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
40 40 | raise IOError
|
||||
41 |-raise WindowsError
|
||||
41 |+raise OSError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 44 | raise IOError(1)
|
||||
|
||||
UP024_2.py:49:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:43:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
48 | raise WindowsError
|
||||
49 | raise EnvironmentError(1)
|
||||
41 | raise WindowsError
|
||||
42 |
|
||||
43 | raise EnvironmentError()
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
50 | raise IOError(1, 2)
|
||||
44 | raise IOError(1)
|
||||
45 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
46 46 | )
|
||||
47 47 |
|
||||
48 48 | raise WindowsError
|
||||
49 |-raise EnvironmentError(1)
|
||||
49 |+raise OSError(1)
|
||||
50 50 | raise IOError(1, 2)
|
||||
40 40 | raise IOError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 |-raise EnvironmentError()
|
||||
43 |+raise OSError()
|
||||
44 44 | raise IOError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
|
||||
UP024_2.py:50:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:44:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
48 | raise WindowsError
|
||||
49 | raise EnvironmentError(1)
|
||||
50 | raise IOError(1, 2)
|
||||
43 | raise EnvironmentError()
|
||||
44 | raise IOError(1)
|
||||
| ^^^^^^^ UP024
|
||||
45 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 |-raise IOError(1)
|
||||
44 |+raise OSError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
47 47 | raise EnvironmentError(
|
||||
|
||||
UP024_2.py:45:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
43 | raise EnvironmentError()
|
||||
44 | raise IOError(1)
|
||||
45 | raise WindowsError(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
46 |
|
||||
47 | raise EnvironmentError(
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 44 | raise IOError(1)
|
||||
45 |-raise WindowsError(1, 2)
|
||||
45 |+raise OSError(1, 2)
|
||||
46 46 |
|
||||
47 47 | raise EnvironmentError(
|
||||
48 48 | 1,
|
||||
|
||||
UP024_2.py:47:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
45 | raise WindowsError(1, 2)
|
||||
46 |
|
||||
47 | raise EnvironmentError(
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
48 | 1,
|
||||
49 | 2,
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | raise IOError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
47 |-raise EnvironmentError(
|
||||
47 |+raise OSError(
|
||||
48 48 | 1,
|
||||
49 49 | 2,
|
||||
50 50 | 3,
|
||||
|
||||
UP024_2.py:53:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
51 | )
|
||||
52 |
|
||||
53 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
54 | raise EnvironmentError(1)
|
||||
55 | raise IOError(1, 2)
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 | 3,
|
||||
51 51 | )
|
||||
52 52 |
|
||||
53 |-raise WindowsError
|
||||
53 |+raise OSError
|
||||
54 54 | raise EnvironmentError(1)
|
||||
55 55 | raise IOError(1, 2)
|
||||
|
||||
UP024_2.py:54:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
53 | raise WindowsError
|
||||
54 | raise EnvironmentError(1)
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
55 | raise IOError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
51 51 | )
|
||||
52 52 |
|
||||
53 53 | raise WindowsError
|
||||
54 |-raise EnvironmentError(1)
|
||||
54 |+raise OSError(1)
|
||||
55 55 | raise IOError(1, 2)
|
||||
|
||||
UP024_2.py:55:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
53 | raise WindowsError
|
||||
54 | raise EnvironmentError(1)
|
||||
55 | raise IOError(1, 2)
|
||||
| ^^^^^^^ UP024
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
47 47 |
|
||||
48 48 | raise WindowsError
|
||||
49 49 | raise EnvironmentError(1)
|
||||
50 |-raise IOError(1, 2)
|
||||
50 |+raise OSError(1, 2)
|
||||
52 52 |
|
||||
53 53 | raise WindowsError
|
||||
54 54 | raise EnvironmentError(1)
|
||||
55 |-raise IOError(1, 2)
|
||||
55 |+raise OSError(1, 2)
|
||||
|
||||
@@ -89,4 +89,15 @@ mod tests {
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fstring_number_format_python_311() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("refurb/FURB116.py"),
|
||||
&settings::LinterSettings::for_rule(Rule::FStringNumberFormat)
|
||||
.with_target_version(PythonVersion::PY311),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Number};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp};
|
||||
use ruff_source_file::find_newline;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -24,6 +25,11 @@ use crate::fix::snippet::SourceCodeSnippet;
|
||||
/// ```python
|
||||
/// print(f"{1337:b}")
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// The fix is only marked as safe for integer literals, all other cases
|
||||
/// are display-only, as they may change the runtime behaviour of the program
|
||||
/// or introduce syntax errors.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FStringNumberFormat {
|
||||
replacement: Option<SourceCodeSnippet>,
|
||||
@@ -121,21 +127,24 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate a replacement, if possible.
|
||||
let replacement = if matches!(
|
||||
arg,
|
||||
Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_)
|
||||
) {
|
||||
let inner_source = checker.locator().slice(arg);
|
||||
|
||||
let quote = checker.stylist().quote();
|
||||
let shorthand = base.shorthand();
|
||||
|
||||
Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}"))
|
||||
let maybe_number = if let Some(maybe_number) = arg
|
||||
.as_unary_op_expr()
|
||||
.filter(|unary_expr| unary_expr.op == UnaryOp::UAdd)
|
||||
.map(|unary_expr| &unary_expr.operand)
|
||||
{
|
||||
maybe_number
|
||||
} else {
|
||||
None
|
||||
arg
|
||||
};
|
||||
|
||||
let applicability = if matches!(maybe_number, Expr::NumberLiteral(_)) {
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::DisplayOnly
|
||||
};
|
||||
|
||||
let replacement = try_create_replacement(checker, arg, base);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
FStringNumberFormat {
|
||||
replacement: replacement.as_deref().map(SourceCodeSnippet::from_str),
|
||||
@@ -145,15 +154,54 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs
|
||||
);
|
||||
|
||||
if let Some(replacement) = replacement {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
replacement,
|
||||
subscript.range(),
|
||||
)));
|
||||
let edit = Edit::range_replacement(replacement, subscript.range());
|
||||
diagnostic.set_fix(Fix::applicable_edit(edit, applicability));
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
|
||||
/// Generate a replacement, if possible.
|
||||
fn try_create_replacement(checker: &Checker, arg: &Expr, base: Base) -> Option<String> {
|
||||
if !matches!(
|
||||
arg,
|
||||
Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_) | Expr::UnaryOp(_)
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inner_source = checker.locator().slice(arg);
|
||||
|
||||
// On Python 3.11 and earlier, trying to replace an `arg` that contains a backslash
|
||||
// would create a `SyntaxError` in the f-string.
|
||||
if checker.target_version() <= PythonVersion::PY311 && inner_source.contains('\\') {
|
||||
return None;
|
||||
}
|
||||
|
||||
// On Python 3.11 and earlier, trying to replace an `arg` that spans multiple lines
|
||||
// would create a `SyntaxError` in the f-string.
|
||||
if checker.target_version() <= PythonVersion::PY311 && find_newline(inner_source).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let quote = checker.stylist().quote();
|
||||
let shorthand = base.shorthand();
|
||||
|
||||
// If the `arg` contains double quotes we need to create the f-string with single quotes
|
||||
// to avoid a `SyntaxError` in Python 3.11 and earlier.
|
||||
if checker.target_version() <= PythonVersion::PY311 && inner_source.contains(quote.as_str()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the `arg` contains a brace add an space before it to avoid a `SyntaxError`
|
||||
// in the f-string.
|
||||
if inner_source.starts_with('{') {
|
||||
Some(format!("f{quote}{{ {inner_source}:{shorthand}}}{quote}"))
|
||||
} else {
|
||||
Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Base {
|
||||
Hex,
|
||||
|
||||
@@ -1,144 +1,288 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB116.py:6:7: FURB116 [*] Replace `oct` call with `f"{num:o}"`
|
||||
|
|
||||
4 | return num
|
||||
5 |
|
||||
6 | print(oct(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
7 | print(hex(num)[2:]) # FURB116
|
||||
8 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:o}"`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | def return_num() -> int:
|
||||
4 4 | return num
|
||||
5 5 |
|
||||
6 |-print(oct(num)[2:]) # FURB116
|
||||
6 |+print(f"{num:o}") # FURB116
|
||||
7 7 | print(hex(num)[2:]) # FURB116
|
||||
8 8 | print(bin(num)[2:]) # FURB116
|
||||
9 9 |
|
||||
|
||||
FURB116.py:7:7: FURB116 [*] Replace `hex` call with `f"{num:x}"`
|
||||
|
|
||||
6 | print(oct(num)[2:]) # FURB116
|
||||
7 | print(hex(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
8 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:x}"`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | return num
|
||||
5 5 |
|
||||
6 6 | print(oct(num)[2:]) # FURB116
|
||||
7 |-print(hex(num)[2:]) # FURB116
|
||||
7 |+print(f"{num:x}") # FURB116
|
||||
8 8 | print(bin(num)[2:]) # FURB116
|
||||
9 9 |
|
||||
10 10 | print(oct(1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:8:7: FURB116 [*] Replace `bin` call with `f"{num:b}"`
|
||||
FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"`
|
||||
|
|
||||
6 | print(oct(num)[2:]) # FURB116
|
||||
7 | print(hex(num)[2:]) # FURB116
|
||||
8 | print(bin(num)[2:]) # FURB116
|
||||
7 | return num
|
||||
8 |
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
9 |
|
||||
10 | print(oct(1337)[2:]) # FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:o}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
6 6 | def return_num() -> int:
|
||||
7 7 | return num
|
||||
8 8 |
|
||||
9 |-print(oct(num)[2:]) # FURB116
|
||||
9 |+print(f"{num:o}") # FURB116
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
|
||||
FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"`
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:x}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
7 7 | return num
|
||||
8 8 |
|
||||
9 9 | print(oct(num)[2:]) # FURB116
|
||||
10 |-print(hex(num)[2:]) # FURB116
|
||||
10 |+print(f"{num:x}") # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"`
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
12 |
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:b}"`
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | print(oct(num)[2:]) # FURB116
|
||||
7 7 | print(hex(num)[2:]) # FURB116
|
||||
8 |-print(bin(num)[2:]) # FURB116
|
||||
8 |+print(f"{num:b}") # FURB116
|
||||
9 9 |
|
||||
10 10 | print(oct(1337)[2:]) # FURB116
|
||||
11 11 | print(hex(1337)[2:]) # FURB116
|
||||
ℹ Display-only fix
|
||||
8 8 |
|
||||
9 9 | print(oct(num)[2:]) # FURB116
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 |-print(bin(num)[2:]) # FURB116
|
||||
11 |+print(f"{num:b}") # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:10:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"`
|
||||
FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"`
|
||||
|
|
||||
8 | print(bin(num)[2:]) # FURB116
|
||||
9 |
|
||||
10 | print(oct(1337)[2:]) # FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
12 |
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
11 | print(hex(1337)[2:]) # FURB116
|
||||
12 | print(bin(1337)[2:]) # FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:o}"`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | print(hex(num)[2:]) # FURB116
|
||||
8 8 | print(bin(num)[2:]) # FURB116
|
||||
9 9 |
|
||||
10 |-print(oct(1337)[2:]) # FURB116
|
||||
10 |+print(f"{1337:o}") # FURB116
|
||||
11 11 | print(hex(1337)[2:]) # FURB116
|
||||
12 12 | print(bin(1337)[2:]) # FURB116
|
||||
13 13 |
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 |-print(oct(1337)[2:]) # FURB116
|
||||
13 |+print(f"{1337:o}") # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:11:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"`
|
||||
FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"`
|
||||
|
|
||||
10 | print(oct(1337)[2:]) # FURB116
|
||||
11 | print(hex(1337)[2:]) # FURB116
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
12 | print(bin(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:x}"`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | print(bin(num)[2:]) # FURB116
|
||||
9 9 |
|
||||
10 10 | print(oct(1337)[2:]) # FURB116
|
||||
11 |-print(hex(1337)[2:]) # FURB116
|
||||
11 |+print(f"{1337:x}") # FURB116
|
||||
12 12 | print(bin(1337)[2:]) # FURB116
|
||||
13 13 |
|
||||
14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 |-print(hex(1337)[2:]) # FURB116
|
||||
14 |+print(f"{1337:x}") # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 17 |
|
||||
|
||||
FURB116.py:12:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"`
|
||||
FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"`
|
||||
|
|
||||
10 | print(oct(1337)[2:]) # FURB116
|
||||
11 | print(hex(1337)[2:]) # FURB116
|
||||
12 | print(bin(1337)[2:]) # FURB116
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
13 |
|
||||
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:b}"`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | print(oct(1337)[2:]) # FURB116
|
||||
11 11 | print(hex(1337)[2:]) # FURB116
|
||||
12 |-print(bin(1337)[2:]) # FURB116
|
||||
12 |+print(f"{1337:b}") # FURB116
|
||||
13 13 |
|
||||
14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
15 15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 |-print(bin(1337)[2:]) # FURB116
|
||||
15 |+print(f"{1337:b}") # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 17 |
|
||||
18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
|
||||
FURB116.py:14:7: FURB116 Replace `bin` call with f-string
|
||||
FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"`
|
||||
|
|
||||
12 | print(bin(1337)[2:]) # FURB116
|
||||
13 |
|
||||
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^^ FURB116
|
||||
17 |
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
|
|
||||
= help: Replace with `f"{+1337:b}"`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 |-print(bin(+1337)[2:]) # FURB116
|
||||
16 |+print(f"{+1337:b}") # FURB116
|
||||
17 17 |
|
||||
18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
|
||||
FURB116.py:18:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 |
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:15:7: FURB116 Replace `bin` call with f-string
|
||||
FURB116.py:19:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
16 |
|
||||
17 | ## invalid
|
||||
20 |
|
||||
21 | ## invalid
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"`
|
||||
|
|
||||
30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
31 | # autofix is display-only
|
||||
32 | print(bin(d)[2:])
|
||||
| ^^^^^^^^^^ FURB116
|
||||
33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 | print(bin(len("xyz").numerator)[2:])
|
||||
|
|
||||
= help: Replace with `f"{d:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
29 29 |
|
||||
30 30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
31 31 | # autofix is display-only
|
||||
32 |-print(bin(d)[2:])
|
||||
32 |+print(f"{d:b}")
|
||||
33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 34 | print(bin(len("xyz").numerator)[2:])
|
||||
35 35 |
|
||||
|
||||
FURB116.py:34:7: FURB116 Replace `bin` call with `f"{len("xyz").numerator:b}"`
|
||||
|
|
||||
32 | print(bin(d)[2:])
|
||||
33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 | print(bin(len("xyz").numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
35 |
|
||||
36 | # autofix is display-only
|
||||
|
|
||||
= help: Replace with `f"{len("xyz").numerator:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
31 31 | # autofix is display-only
|
||||
32 32 | print(bin(d)[2:])
|
||||
33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 |-print(bin(len("xyz").numerator)[2:])
|
||||
34 |+print(f"{len("xyz").numerator:b}")
|
||||
35 35 |
|
||||
36 36 | # autofix is display-only
|
||||
37 37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
|
||||
FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
|
|
||||
36 | # autofix is display-only
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
|
|
||||
= help: Replace with `f"{ {0: 1}[0].numerator:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
34 34 | print(bin(len("xyz").numerator)[2:])
|
||||
35 35 |
|
||||
36 36 | # autofix is display-only
|
||||
37 |-print(bin({0: 1}[0].numerator)[2:])
|
||||
37 |+print(f"{ {0: 1}[0].numerator:b}")
|
||||
38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 39 | print(bin(ord("\\").numerator)[2:])
|
||||
40 40 | print(hex(sys
|
||||
|
||||
FURB116.py:39:7: FURB116 Replace `bin` call with `f"{ord("\\").numerator:b}"`
|
||||
|
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
40 | print(hex(sys
|
||||
41 | .maxunicode)[2:])
|
||||
|
|
||||
= help: Replace with `f"{ord("\\").numerator:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
36 36 | # autofix is display-only
|
||||
37 37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 |-print(bin(ord("\\").numerator)[2:])
|
||||
39 |+print(f"{ord("\\").numerator:b}")
|
||||
40 40 | print(hex(sys
|
||||
41 41 | .maxunicode)[2:])
|
||||
42 42 |
|
||||
|
||||
FURB116.py:40:7: FURB116 Replace `hex` call with f-string
|
||||
|
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
40 | print(hex(sys
|
||||
| _______^
|
||||
41 | | .maxunicode)[2:])
|
||||
| |________________^ FURB116
|
||||
42 |
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
ℹ Display-only fix
|
||||
37 37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 39 | print(bin(ord("\\").numerator)[2:])
|
||||
40 |-print(hex(sys
|
||||
41 |-.maxunicode)[2:])
|
||||
40 |+print(f"{sys
|
||||
41 |+.maxunicode:x}")
|
||||
42 42 |
|
||||
43 43 | # for negatives numbers autofix is display-only
|
||||
44 44 | print(bin(-1)[2:])
|
||||
|
||||
FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"`
|
||||
|
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
44 | print(bin(-1)[2:])
|
||||
| ^^^^^^^^^^^ FURB116
|
||||
|
|
||||
= help: Replace with `f"{-1:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
41 41 | .maxunicode)[2:])
|
||||
42 42 |
|
||||
43 43 | # for negatives numbers autofix is display-only
|
||||
44 |-print(bin(-1)[2:])
|
||||
44 |+print(f"{-1:b}")
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"`
|
||||
|
|
||||
7 | return num
|
||||
8 |
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:o}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
6 6 | def return_num() -> int:
|
||||
7 7 | return num
|
||||
8 8 |
|
||||
9 |-print(oct(num)[2:]) # FURB116
|
||||
9 |+print(f"{num:o}") # FURB116
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
|
||||
FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"`
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:x}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
7 7 | return num
|
||||
8 8 |
|
||||
9 9 | print(oct(num)[2:]) # FURB116
|
||||
10 |-print(hex(num)[2:]) # FURB116
|
||||
10 |+print(f"{num:x}") # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"`
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
10 | print(hex(num)[2:]) # FURB116
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^ FURB116
|
||||
12 |
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{num:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
8 8 |
|
||||
9 9 | print(oct(num)[2:]) # FURB116
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 |-print(bin(num)[2:]) # FURB116
|
||||
11 |+print(f"{num:b}") # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"`
|
||||
|
|
||||
11 | print(bin(num)[2:]) # FURB116
|
||||
12 |
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:o}"`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | print(hex(num)[2:]) # FURB116
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 |-print(oct(1337)[2:]) # FURB116
|
||||
13 |+print(f"{1337:o}") # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
||||
FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"`
|
||||
|
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:x}"`
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | print(bin(num)[2:]) # FURB116
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 |-print(hex(1337)[2:]) # FURB116
|
||||
14 |+print(f"{1337:x}") # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 17 |
|
||||
|
||||
FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"`
|
||||
|
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^ FURB116
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
|
|
||||
= help: Replace with `f"{1337:b}"`
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 |
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 |-print(bin(1337)[2:]) # FURB116
|
||||
15 |+print(f"{1337:b}") # FURB116
|
||||
16 16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 17 |
|
||||
18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
|
||||
FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"`
|
||||
|
|
||||
14 | print(hex(1337)[2:]) # FURB116
|
||||
15 | print(bin(1337)[2:]) # FURB116
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
| ^^^^^^^^^^^^^^ FURB116
|
||||
17 |
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
|
|
||||
= help: Replace with `f"{+1337:b}"`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | print(oct(1337)[2:]) # FURB116
|
||||
14 14 | print(hex(1337)[2:]) # FURB116
|
||||
15 15 | print(bin(1337)[2:]) # FURB116
|
||||
16 |-print(bin(+1337)[2:]) # FURB116
|
||||
16 |+print(f"{+1337:b}") # FURB116
|
||||
17 17 |
|
||||
18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
|
||||
FURB116.py:18:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
16 | print(bin(+1337)[2:]) # FURB116
|
||||
17 |
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:19:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
18 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
||||
19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
20 |
|
||||
21 | ## invalid
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"`
|
||||
|
|
||||
30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
31 | # autofix is display-only
|
||||
32 | print(bin(d)[2:])
|
||||
| ^^^^^^^^^^ FURB116
|
||||
33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 | print(bin(len("xyz").numerator)[2:])
|
||||
|
|
||||
= help: Replace with `f"{d:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
29 29 |
|
||||
30 30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
31 31 | # autofix is display-only
|
||||
32 |-print(bin(d)[2:])
|
||||
32 |+print(f"{d:b}")
|
||||
33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 34 | print(bin(len("xyz").numerator)[2:])
|
||||
35 35 |
|
||||
|
||||
FURB116.py:34:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
32 | print(bin(d)[2:])
|
||||
33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
34 | print(bin(len("xyz").numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
35 |
|
||||
36 | # autofix is display-only
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
|
|
||||
36 | # autofix is display-only
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
|
|
||||
= help: Replace with `f"{ {0: 1}[0].numerator:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
34 34 | print(bin(len("xyz").numerator)[2:])
|
||||
35 35 |
|
||||
36 36 | # autofix is display-only
|
||||
37 |-print(bin({0: 1}[0].numerator)[2:])
|
||||
37 |+print(f"{ {0: 1}[0].numerator:b}")
|
||||
38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 39 | print(bin(ord("\\").numerator)[2:])
|
||||
40 40 | print(hex(sys
|
||||
|
||||
FURB116.py:39:7: FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116
|
||||
40 | print(hex(sys
|
||||
41 | .maxunicode)[2:])
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:40:7: FURB116 Replace `hex` call with f-string
|
||||
|
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
39 | print(bin(ord("\\").numerator)[2:])
|
||||
40 | print(hex(sys
|
||||
| _______^
|
||||
41 | | .maxunicode)[2:])
|
||||
| |________________^ FURB116
|
||||
42 |
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
|
|
||||
= help: Replace with f-string
|
||||
|
||||
FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"`
|
||||
|
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
44 | print(bin(-1)[2:])
|
||||
| ^^^^^^^^^^^ FURB116
|
||||
|
|
||||
= help: Replace with `f"{-1:b}"`
|
||||
|
||||
ℹ Display-only fix
|
||||
41 41 | .maxunicode)[2:])
|
||||
42 42 |
|
||||
43 43 | # for negatives numbers autofix is display-only
|
||||
44 |-print(bin(-1)[2:])
|
||||
44 |+print(f"{-1:b}")
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -48,6 +49,14 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
||||
};
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if is_empty(right, semantic) {
|
||||
checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range()));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let set_methods = ["set", "frozenset"];
|
||||
let collection_methods = [
|
||||
"list",
|
||||
"tuple",
|
||||
@@ -59,7 +68,7 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
||||
"str",
|
||||
];
|
||||
|
||||
let is_empty_collection = match right {
|
||||
match expr {
|
||||
Expr::List(ast::ExprList { elts, .. }) => elts.is_empty(),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.is_empty(),
|
||||
Expr::Set(ast::ExprSet { elts, .. }) => elts.is_empty(),
|
||||
@@ -75,15 +84,19 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
||||
arguments,
|
||||
range: _,
|
||||
}) => {
|
||||
arguments.is_empty()
|
||||
&& collection_methods
|
||||
if arguments.is_empty() {
|
||||
collection_methods
|
||||
.iter()
|
||||
.any(|s| semantic.match_builtin_expr(func, s))
|
||||
} else if let Some(arg) = arguments.find_positional(0) {
|
||||
set_methods
|
||||
.iter()
|
||||
.any(|s| semantic.match_builtin_expr(func, s))
|
||||
&& is_empty(arg, semantic)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_empty_collection {
|
||||
checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ use super::helpers::{dataclass_kind, DataclassKind};
|
||||
/// foo = Foo() # Prints '1 2'.
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// This fix is always marked as unsafe because, although switching to `InitVar` is usually correct,
|
||||
/// it is incorrect when the parameter is not intended to be part of the public API or when the value
|
||||
/// is meant to be shared across all instances.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Post-init processing](https://docs.python.org/3/library/dataclasses.html#post-init-processing)
|
||||
/// - [Python documentation: Init-only variables](https://docs.python.org/3/library/dataclasses.html#init-only-variables)
|
||||
|
||||
@@ -29,6 +29,14 @@ use crate::{checkers::ast::Checker, importer::ImportRequest};
|
||||
/// pairwise(letters) # ("A", "B"), ("B", "C"), ("C", "D")
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// The fix is always marked unsafe because it assumes that slicing an object
|
||||
/// (e.g., `obj[1:]`) produces a value with the same type and iteration behavior
|
||||
/// as the original object, which is not guaranteed for user-defined types that
|
||||
/// override `__getitem__` without properly handling slices. Moreover, the fix
|
||||
/// could delete comments.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `itertools.pairwise`](https://docs.python.org/3/library/itertools.html#itertools.pairwise)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -187,6 +187,7 @@ RUF060.py:20:1: RUF060 Unnecessary membership test on empty collection
|
||||
20 | b"a" in bytes()
|
||||
| ^^^^^^^^^^^^^^^ RUF060
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
|
|
||||
|
||||
RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection
|
||||
@@ -195,6 +196,35 @@ RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection
|
||||
20 | b"a" in bytes()
|
||||
21 | 1 in frozenset()
|
||||
| ^^^^^^^^^^^^^^^^ RUF060
|
||||
22 |
|
||||
23 | # OK
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
|
|
||||
|
||||
RUF060.py:22:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
20 | b"a" in bytes()
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
| ^^^^^^^^^^^^^^^ RUF060
|
||||
23 | 2 in frozenset([])
|
||||
24 | '' in set("")
|
||||
|
|
||||
|
||||
RUF060.py:23:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF060
|
||||
24 | '' in set("")
|
||||
|
|
||||
|
||||
RUF060.py:24:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
24 | '' in set("")
|
||||
| ^^^^^^^^^^^^^ RUF060
|
||||
25 |
|
||||
26 | # OK
|
||||
|
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:22: SyntaxError: named expression cannot be used within a generic definition
|
||||
|
|
||||
2 | def f[T](x: int) -> (y := 3): return x
|
||||
| ^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:13: SyntaxError: yield expression cannot be used within a generic definition
|
||||
|
|
||||
2 | class C[T]((yield from [object])):
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
3 | pass
|
||||
|
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:11: SyntaxError: yield expression cannot be used within a type alias
|
||||
|
|
||||
2 | type Y = (yield 1)
|
||||
| ^^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:12: SyntaxError: yield expression cannot be used within a TypeVar bound
|
||||
|
|
||||
2 | type X[T: (yield 1)] = int
|
||||
| ^^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:3:10: SyntaxError: name capture `irrefutable` makes remaining patterns unreachable
|
||||
|
|
||||
2 | match value:
|
||||
3 | case irrefutable:
|
||||
| ^^^^^^^^^^^
|
||||
4 | pass
|
||||
5 | case 1:
|
||||
|
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:3:10: SyntaxError: wildcard makes remaining patterns unreachable
|
||||
|
|
||||
2 | match value:
|
||||
3 | case _:
|
||||
| ^
|
||||
4 | pass
|
||||
5 | case 1:
|
||||
|
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:1:1: SyntaxError: starred assignment target must be in a list or tuple
|
||||
|
|
||||
1 | *a = [1, 2, 3, 4]
|
||||
| ^^
|
||||
|
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:1: SyntaxError: cannot assign to `__debug__`
|
||||
|
|
||||
2 | __debug__ = False
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:15: SyntaxError: cannot assign to `__debug__`
|
||||
|
|
||||
2 | class Generic[__debug__]:
|
||||
| ^^^^^^^^^
|
||||
3 | pass
|
||||
|
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:2:13: SyntaxError: cannot assign to `__debug__`
|
||||
|
|
||||
2 | def process(__debug__):
|
||||
| ^^^^^^^^^
|
||||
3 | pass
|
||||
|
|
||||
@@ -2844,7 +2844,7 @@ impl Arguments {
|
||||
self.find_argument(name, position).map(ArgOrKeyword::value)
|
||||
}
|
||||
|
||||
/// Return the the argument with the given name or at the given position, or `None` if no such
|
||||
/// Return the argument with the given name or at the given position, or `None` if no such
|
||||
/// argument exists. Used to retrieve arguments that can be provided _either_ as keyword or
|
||||
/// positional arguments.
|
||||
pub fn find_argument(&self, name: &str, position: usize) -> Option<ArgOrKeyword> {
|
||||
|
||||
@@ -590,7 +590,7 @@ pub fn walk_except_handler<'a, V: Visitor<'a> + ?Sized>(
|
||||
}
|
||||
|
||||
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
|
||||
// Note that the there might be keywords before the last arg, e.g. in
|
||||
// Note that there might be keywords before the last arg, e.g. in
|
||||
// f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then
|
||||
// `keywords`. See also [Arguments::arguments_source_order`].
|
||||
for arg in &*arguments.args {
|
||||
|
||||
@@ -234,9 +234,6 @@ pub fn walk_stmt<V: Transformer + ?Sized>(visitor: &V, stmt: &mut Stmt) {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_body(body);
|
||||
for clause in elif_else_clauses {
|
||||
if let Some(test) = &mut clause.test {
|
||||
visitor.visit_expr(test);
|
||||
}
|
||||
walk_elif_else_clause(visitor, clause);
|
||||
}
|
||||
}
|
||||
@@ -576,7 +573,7 @@ pub fn walk_except_handler<V: Transformer + ?Sized>(
|
||||
}
|
||||
|
||||
pub fn walk_arguments<V: Transformer + ?Sized>(visitor: &V, arguments: &mut Arguments) {
|
||||
// Note that the there might be keywords before the last arg, e.g. in
|
||||
// Note that there might be keywords before the last arg, e.g. in
|
||||
// f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then
|
||||
// `keywords`. See also [Arguments::arguments_source_order`].
|
||||
for arg in &mut *arguments.args {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# For tuple expression, the minimum binding power of star expression is bitwise or.
|
||||
# Test the first and any other element as the there are two separate calls.
|
||||
# Test the first and any other element as there are two separate calls.
|
||||
|
||||
(*x in y, z, *x in y)
|
||||
(*not x, z, *not x)
|
||||
|
||||
@@ -7,24 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..536,
|
||||
range: 0..532,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 161..182,
|
||||
range: 157..178,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 161..182,
|
||||
range: 157..178,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 162..169,
|
||||
range: 158..165,
|
||||
value: Compare(
|
||||
ExprCompare {
|
||||
range: 163..169,
|
||||
range: 159..165,
|
||||
left: Name(
|
||||
ExprName {
|
||||
range: 163..164,
|
||||
range: 159..160,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -35,7 +35,7 @@ Module(
|
||||
comparators: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 168..169,
|
||||
range: 164..165,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -48,20 +48,20 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 171..172,
|
||||
range: 167..168,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 174..181,
|
||||
range: 170..177,
|
||||
value: Compare(
|
||||
ExprCompare {
|
||||
range: 175..181,
|
||||
range: 171..177,
|
||||
left: Name(
|
||||
ExprName {
|
||||
range: 175..176,
|
||||
range: 171..172,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -72,7 +72,7 @@ Module(
|
||||
comparators: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 180..181,
|
||||
range: 176..177,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -92,21 +92,21 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 183..202,
|
||||
range: 179..198,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 183..202,
|
||||
range: 179..198,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 184..190,
|
||||
range: 180..186,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 185..190,
|
||||
range: 181..186,
|
||||
op: Not,
|
||||
operand: Name(
|
||||
ExprName {
|
||||
range: 189..190,
|
||||
range: 185..186,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -118,21 +118,21 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 192..193,
|
||||
range: 188..189,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 195..201,
|
||||
range: 191..197,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 196..201,
|
||||
range: 192..197,
|
||||
op: Not,
|
||||
operand: Name(
|
||||
ExprName {
|
||||
range: 200..201,
|
||||
range: 196..197,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -151,29 +151,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 203..226,
|
||||
range: 199..222,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 203..226,
|
||||
range: 199..222,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 204..212,
|
||||
range: 200..208,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 205..212,
|
||||
range: 201..208,
|
||||
op: And,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 205..206,
|
||||
range: 201..202,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 211..212,
|
||||
range: 207..208,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -186,29 +186,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 214..215,
|
||||
range: 210..211,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 217..225,
|
||||
range: 213..221,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 218..225,
|
||||
range: 214..221,
|
||||
op: And,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 218..219,
|
||||
range: 214..215,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 224..225,
|
||||
range: 220..221,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -228,29 +228,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 227..248,
|
||||
range: 223..244,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 227..248,
|
||||
range: 223..244,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 228..235,
|
||||
range: 224..231,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 229..235,
|
||||
range: 225..231,
|
||||
op: Or,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 229..230,
|
||||
range: 225..226,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 234..235,
|
||||
range: 230..231,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -263,29 +263,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 237..238,
|
||||
range: 233..234,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 240..247,
|
||||
range: 236..243,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 241..247,
|
||||
range: 237..243,
|
||||
op: Or,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 241..242,
|
||||
range: 237..238,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 246..247,
|
||||
range: 242..243,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -305,33 +305,33 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 249..290,
|
||||
range: 245..286,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 249..290,
|
||||
range: 245..286,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 250..267,
|
||||
range: 246..263,
|
||||
value: If(
|
||||
ExprIf {
|
||||
range: 251..267,
|
||||
range: 247..263,
|
||||
test: BooleanLiteral(
|
||||
ExprBooleanLiteral {
|
||||
range: 256..260,
|
||||
range: 252..256,
|
||||
value: true,
|
||||
},
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 251..252,
|
||||
range: 247..248,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
orelse: Name(
|
||||
ExprName {
|
||||
range: 266..267,
|
||||
range: 262..263,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -343,33 +343,33 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 269..270,
|
||||
range: 265..266,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 272..289,
|
||||
range: 268..285,
|
||||
value: If(
|
||||
ExprIf {
|
||||
range: 273..289,
|
||||
range: 269..285,
|
||||
test: BooleanLiteral(
|
||||
ExprBooleanLiteral {
|
||||
range: 278..282,
|
||||
range: 274..278,
|
||||
value: true,
|
||||
},
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 273..274,
|
||||
range: 269..270,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
orelse: Name(
|
||||
ExprName {
|
||||
range: 288..289,
|
||||
range: 284..285,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -388,29 +388,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 291..322,
|
||||
range: 287..318,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 291..322,
|
||||
range: 287..318,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 292..304,
|
||||
range: 288..300,
|
||||
value: Lambda(
|
||||
ExprLambda {
|
||||
range: 293..304,
|
||||
range: 289..300,
|
||||
parameters: Some(
|
||||
Parameters {
|
||||
range: 300..301,
|
||||
range: 296..297,
|
||||
posonlyargs: [],
|
||||
args: [
|
||||
ParameterWithDefault {
|
||||
range: 300..301,
|
||||
range: 296..297,
|
||||
parameter: Parameter {
|
||||
range: 300..301,
|
||||
range: 296..297,
|
||||
name: Identifier {
|
||||
id: Name("x"),
|
||||
range: 300..301,
|
||||
range: 296..297,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
||||
@@ -424,7 +424,7 @@ Module(
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 303..304,
|
||||
range: 299..300,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -436,29 +436,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 306..307,
|
||||
range: 302..303,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 309..321,
|
||||
range: 305..317,
|
||||
value: Lambda(
|
||||
ExprLambda {
|
||||
range: 310..321,
|
||||
range: 306..317,
|
||||
parameters: Some(
|
||||
Parameters {
|
||||
range: 317..318,
|
||||
range: 313..314,
|
||||
posonlyargs: [],
|
||||
args: [
|
||||
ParameterWithDefault {
|
||||
range: 317..318,
|
||||
range: 313..314,
|
||||
parameter: Parameter {
|
||||
range: 317..318,
|
||||
range: 313..314,
|
||||
name: Identifier {
|
||||
id: Name("x"),
|
||||
range: 317..318,
|
||||
range: 313..314,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
||||
@@ -472,7 +472,7 @@ Module(
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 320..321,
|
||||
range: 316..317,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -491,20 +491,20 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 323..344,
|
||||
range: 319..340,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 323..344,
|
||||
range: 319..340,
|
||||
elts: [
|
||||
Named(
|
||||
ExprNamed {
|
||||
range: 324..331,
|
||||
range: 320..327,
|
||||
target: Starred(
|
||||
ExprStarred {
|
||||
range: 324..326,
|
||||
range: 320..322,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 325..326,
|
||||
range: 321..322,
|
||||
id: Name("x"),
|
||||
ctx: Store,
|
||||
},
|
||||
@@ -514,7 +514,7 @@ Module(
|
||||
),
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 330..331,
|
||||
range: 326..327,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
@@ -524,20 +524,20 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 333..334,
|
||||
range: 329..330,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Named(
|
||||
ExprNamed {
|
||||
range: 336..343,
|
||||
range: 332..339,
|
||||
target: Starred(
|
||||
ExprStarred {
|
||||
range: 336..338,
|
||||
range: 332..334,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 337..338,
|
||||
range: 333..334,
|
||||
id: Name("x"),
|
||||
ctx: Store,
|
||||
},
|
||||
@@ -547,7 +547,7 @@ Module(
|
||||
),
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 342..343,
|
||||
range: 338..339,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
@@ -564,20 +564,20 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 367..386,
|
||||
range: 363..382,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 367..386,
|
||||
range: 363..382,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 367..374,
|
||||
range: 363..370,
|
||||
value: Compare(
|
||||
ExprCompare {
|
||||
range: 368..374,
|
||||
range: 364..370,
|
||||
left: Name(
|
||||
ExprName {
|
||||
range: 368..369,
|
||||
range: 364..365,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -588,7 +588,7 @@ Module(
|
||||
comparators: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 373..374,
|
||||
range: 369..370,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -601,20 +601,20 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 376..377,
|
||||
range: 372..373,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 379..386,
|
||||
range: 375..382,
|
||||
value: Compare(
|
||||
ExprCompare {
|
||||
range: 380..386,
|
||||
range: 376..382,
|
||||
left: Name(
|
||||
ExprName {
|
||||
range: 380..381,
|
||||
range: 376..377,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -625,7 +625,7 @@ Module(
|
||||
comparators: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 385..386,
|
||||
range: 381..382,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -645,21 +645,21 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 387..404,
|
||||
range: 383..400,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 387..404,
|
||||
range: 383..400,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 387..393,
|
||||
range: 383..389,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 388..393,
|
||||
range: 384..389,
|
||||
op: Not,
|
||||
operand: Name(
|
||||
ExprName {
|
||||
range: 392..393,
|
||||
range: 388..389,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -671,21 +671,21 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 395..396,
|
||||
range: 391..392,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 398..404,
|
||||
range: 394..400,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 399..404,
|
||||
range: 395..400,
|
||||
op: Not,
|
||||
operand: Name(
|
||||
ExprName {
|
||||
range: 403..404,
|
||||
range: 399..400,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -704,29 +704,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 405..426,
|
||||
range: 401..422,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 405..426,
|
||||
range: 401..422,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 405..413,
|
||||
range: 401..409,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 406..413,
|
||||
range: 402..409,
|
||||
op: And,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 406..407,
|
||||
range: 402..403,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 412..413,
|
||||
range: 408..409,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -739,29 +739,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 415..416,
|
||||
range: 411..412,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 418..426,
|
||||
range: 414..422,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 419..426,
|
||||
range: 415..422,
|
||||
op: And,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 419..420,
|
||||
range: 415..416,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 425..426,
|
||||
range: 421..422,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -781,29 +781,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 427..446,
|
||||
range: 423..442,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 427..446,
|
||||
range: 423..442,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 427..434,
|
||||
range: 423..430,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 428..434,
|
||||
range: 424..430,
|
||||
op: Or,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 428..429,
|
||||
range: 424..425,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 433..434,
|
||||
range: 429..430,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -816,29 +816,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 436..437,
|
||||
range: 432..433,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 439..446,
|
||||
range: 435..442,
|
||||
value: BoolOp(
|
||||
ExprBoolOp {
|
||||
range: 440..446,
|
||||
range: 436..442,
|
||||
op: Or,
|
||||
values: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 440..441,
|
||||
range: 436..437,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 445..446,
|
||||
range: 441..442,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -858,33 +858,33 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 447..486,
|
||||
range: 443..482,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 447..486,
|
||||
range: 443..482,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 447..464,
|
||||
range: 443..460,
|
||||
value: If(
|
||||
ExprIf {
|
||||
range: 448..464,
|
||||
range: 444..460,
|
||||
test: BooleanLiteral(
|
||||
ExprBooleanLiteral {
|
||||
range: 453..457,
|
||||
range: 449..453,
|
||||
value: true,
|
||||
},
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 448..449,
|
||||
range: 444..445,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
orelse: Name(
|
||||
ExprName {
|
||||
range: 463..464,
|
||||
range: 459..460,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -896,33 +896,33 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 466..467,
|
||||
range: 462..463,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 469..486,
|
||||
range: 465..482,
|
||||
value: If(
|
||||
ExprIf {
|
||||
range: 470..486,
|
||||
range: 466..482,
|
||||
test: BooleanLiteral(
|
||||
ExprBooleanLiteral {
|
||||
range: 475..479,
|
||||
range: 471..475,
|
||||
value: true,
|
||||
},
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 470..471,
|
||||
range: 466..467,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
orelse: Name(
|
||||
ExprName {
|
||||
range: 485..486,
|
||||
range: 481..482,
|
||||
id: Name("y"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -941,29 +941,29 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 487..516,
|
||||
range: 483..512,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 487..516,
|
||||
range: 483..512,
|
||||
elts: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 487..499,
|
||||
range: 483..495,
|
||||
value: Lambda(
|
||||
ExprLambda {
|
||||
range: 488..499,
|
||||
range: 484..495,
|
||||
parameters: Some(
|
||||
Parameters {
|
||||
range: 495..496,
|
||||
range: 491..492,
|
||||
posonlyargs: [],
|
||||
args: [
|
||||
ParameterWithDefault {
|
||||
range: 495..496,
|
||||
range: 491..492,
|
||||
parameter: Parameter {
|
||||
range: 495..496,
|
||||
range: 491..492,
|
||||
name: Identifier {
|
||||
id: Name("x"),
|
||||
range: 495..496,
|
||||
range: 491..492,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
||||
@@ -977,7 +977,7 @@ Module(
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 498..499,
|
||||
range: 494..495,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -989,29 +989,29 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 501..502,
|
||||
range: 497..498,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 504..516,
|
||||
range: 500..512,
|
||||
value: Lambda(
|
||||
ExprLambda {
|
||||
range: 505..516,
|
||||
range: 501..512,
|
||||
parameters: Some(
|
||||
Parameters {
|
||||
range: 512..513,
|
||||
range: 508..509,
|
||||
posonlyargs: [],
|
||||
args: [
|
||||
ParameterWithDefault {
|
||||
range: 512..513,
|
||||
range: 508..509,
|
||||
parameter: Parameter {
|
||||
range: 512..513,
|
||||
range: 508..509,
|
||||
name: Identifier {
|
||||
id: Name("x"),
|
||||
range: 512..513,
|
||||
range: 508..509,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
||||
@@ -1025,7 +1025,7 @@ Module(
|
||||
),
|
||||
body: Name(
|
||||
ExprName {
|
||||
range: 515..516,
|
||||
range: 511..512,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -1044,13 +1044,13 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 517..519,
|
||||
range: 513..515,
|
||||
value: Starred(
|
||||
ExprStarred {
|
||||
range: 517..519,
|
||||
range: 513..515,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 518..519,
|
||||
range: 514..515,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -1062,14 +1062,14 @@ Module(
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 523..536,
|
||||
range: 519..532,
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 523..536,
|
||||
range: 519..532,
|
||||
elts: [
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 523..524,
|
||||
range: 519..520,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
@@ -1077,17 +1077,17 @@ Module(
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 526..527,
|
||||
range: 522..523,
|
||||
id: Name("z"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 529..531,
|
||||
range: 525..527,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 530..531,
|
||||
range: 526..527,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
@@ -1097,7 +1097,7 @@ Module(
|
||||
),
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 535..536,
|
||||
range: 531..532,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
@@ -1117,7 +1117,7 @@ Module(
|
||||
## Errors
|
||||
|
||||
|
|
||||
2 | # Test the first and any other element as the there are two separate calls.
|
||||
2 | # Test the first and any other element as there are two separate calls.
|
||||
3 |
|
||||
4 | (*x in y, z, *x in y)
|
||||
| ^^^^^^ Syntax Error: Comparison expression cannot be used here
|
||||
@@ -1127,7 +1127,7 @@ Module(
|
||||
|
||||
|
||||
|
|
||||
2 | # Test the first and any other element as the there are two separate calls.
|
||||
2 | # Test the first and any other element as there are two separate calls.
|
||||
3 |
|
||||
4 | (*x in y, z, *x in y)
|
||||
| ^^^^^^ Syntax Error: Comparison expression cannot be used here
|
||||
|
||||
@@ -23,7 +23,6 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "_collections"
|
||||
| "_collections_abc"
|
||||
| "_compat_pickle"
|
||||
| "_compression"
|
||||
| "_contextvars"
|
||||
| "_csv"
|
||||
| "_ctypes"
|
||||
@@ -285,6 +284,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
) | (
|
||||
7,
|
||||
"_bootlocale"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_dummy_thread"
|
||||
| "_msi"
|
||||
@@ -324,6 +324,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
) | (
|
||||
8,
|
||||
"_bootlocale"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_dummy_thread"
|
||||
| "_msi"
|
||||
@@ -368,6 +369,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
"_aix_support"
|
||||
| "_bootlocale"
|
||||
| "_bootsubprocess"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_msi"
|
||||
| "_peg_parser"
|
||||
@@ -399,7 +401,6 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "nntplib"
|
||||
| "ossaudiodev"
|
||||
| "parser"
|
||||
| "peg_parser"
|
||||
| "pipes"
|
||||
| "smtpd"
|
||||
| "sndhdr"
|
||||
@@ -414,6 +415,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
10,
|
||||
"_aix_support"
|
||||
| "_bootsubprocess"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_msi"
|
||||
| "_posixshmem"
|
||||
@@ -460,6 +462,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "__phello_alias__"
|
||||
| "_aix_support"
|
||||
| "_bootsubprocess"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_msi"
|
||||
| "_posixshmem"
|
||||
@@ -507,6 +510,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "__hello_only__"
|
||||
| "__phello_alias__"
|
||||
| "_aix_support"
|
||||
| "_compression"
|
||||
| "_crypt"
|
||||
| "_msi"
|
||||
| "_posixshmem"
|
||||
@@ -554,7 +558,9 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "__phello_alias__"
|
||||
| "_aix_support"
|
||||
| "_android_support"
|
||||
| "_apple_support"
|
||||
| "_colorize"
|
||||
| "_compression"
|
||||
| "_interpchannels"
|
||||
| "_interpqueues"
|
||||
| "_interpreters"
|
||||
@@ -583,6 +589,50 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
| "tomllib"
|
||||
| "xxlimited_35"
|
||||
| "zoneinfo"
|
||||
) | (
|
||||
14,
|
||||
"__hello_alias__"
|
||||
| "__hello_only__"
|
||||
| "__phello_alias__"
|
||||
| "_aix_support"
|
||||
| "_android_support"
|
||||
| "_apple_support"
|
||||
| "_ast_unparse"
|
||||
| "_colorize"
|
||||
| "_hmac"
|
||||
| "_interpchannels"
|
||||
| "_interpqueues"
|
||||
| "_interpreters"
|
||||
| "_ios_support"
|
||||
| "_opcode_metadata"
|
||||
| "_posixshmem"
|
||||
| "_py_warnings"
|
||||
| "_pydatetime"
|
||||
| "_pylong"
|
||||
| "_pyrepl"
|
||||
| "_remote_debugging"
|
||||
| "_sha2"
|
||||
| "_statistics"
|
||||
| "_suggestions"
|
||||
| "_sysconfig"
|
||||
| "_testcapi_datetime"
|
||||
| "_testclinic"
|
||||
| "_testclinic_limited"
|
||||
| "_testinternalcapi"
|
||||
| "_testlimitedcapi"
|
||||
| "_testsinglephase"
|
||||
| "_tokenize"
|
||||
| "_types"
|
||||
| "_typing"
|
||||
| "_wmi"
|
||||
| "_zoneinfo"
|
||||
| "_zstd"
|
||||
| "annotationlib"
|
||||
| "compression"
|
||||
| "graphlib"
|
||||
| "tomllib"
|
||||
| "xxlimited_35"
|
||||
| "zoneinfo"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
141
crates/ty/CONTRIBUTING.md
Normal file
141
crates/ty/CONTRIBUTING.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Contributing to ty
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to ty.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This guide is for ty. If you're looking to contribute to Ruff, please see
|
||||
> [the Ruff contributing guide](../../CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
|
||||
We welcome contributions in the form of pull requests.
|
||||
|
||||
For small changes (e.g., bug fixes), feel free to submit a PR.
|
||||
|
||||
For larger changes (e.g. new diagnostics, new functionality, new configuration options), consider
|
||||
creating an [**issue**](https://github.com/astral-sh/ty/issues) outlining your proposed change.
|
||||
You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the
|
||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
|
||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
that are ready for contributions.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
ty is written in Rust. You'll need to install the
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
|
||||
run Python utility commands.
|
||||
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
uv tool install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run ty's test suite (via `cargo nextest run`),
|
||||
though it's not strictly necessary:
|
||||
|
||||
```shell
|
||||
cargo install cargo-nextest --locked
|
||||
```
|
||||
|
||||
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
|
||||
if you choose to install `nextest`.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ty locally from the repository root with:
|
||||
|
||||
```shell
|
||||
cargo run --bin ty -- check --project /path/to/project/
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted,
|
||||
and that it passes both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
|
||||
cargo test # Rust testing
|
||||
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your pull request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
If you're using VS Code, you can also install the recommended [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension to get these checks while editing.
|
||||
|
||||
Include the text `[ty]` at the beginning of your pull request title, to distinguish ty pull requests
|
||||
from Ruff ones.
|
||||
|
||||
Your pull request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Debugging ty
|
||||
|
||||
ty can optionally emit extensive tracing output, which can be very useful in understanding its
|
||||
operation and debugging issues; see [`crates/ty/docs/tracing.md`](./docs/tracing.md) for details.
|
||||
|
||||
### Project Structure
|
||||
|
||||
The codebase is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
|
||||
such that all crates are contained in a flat `crates` directory.
|
||||
|
||||
The vast majority of ty's code lives in the `ty_python_semantic` crate (located at
|
||||
`crates/ty_python_semantic`). As a contributor, that's the crate that'll probably be most relevant
|
||||
to you.
|
||||
|
||||
At the time of writing, the repository includes the following ty-specific crates (in addition to
|
||||
crates shared with Ruff, such as `ruff_db`, `ruff_python_ast`, and `ruff_python_parser`):
|
||||
|
||||
- `ty_python_semantic`: The core type checker, which includes the type inference engine and
|
||||
semantic analysis.
|
||||
- `ty_test`: The Markdown-based test framework for ty, "mdtest".
|
||||
- `ty`: The command-line interface.
|
||||
- `ty_ide`: IDE features (hover, go-to-definition, autocomplete) for the language server.
|
||||
- `ty_project`: Discovery and representation of a Python project to be checked by ty.
|
||||
- `ty_server`: The ty language server.
|
||||
- `ty_vendored`: A vendored copy of [typeshed](https://github.com/python/typeshed), which holds type
|
||||
annotations for the Python standard library.
|
||||
- `ty_wasm`: library crate for exposing ty as a WebAssembly module. Powers the
|
||||
[ty Playground](https://play.ty.dev/).
|
||||
|
||||
## Writing tests
|
||||
|
||||
Core type checking tests are written as Markdown code blocks.
|
||||
They can be found in [`crates/ty_python_semantic/resources/mdtest`][resources-mdtest].
|
||||
See [`crates/ty_test/README.md`][mdtest-readme] for more information
|
||||
on the test framework itself.
|
||||
|
||||
Any ty pull request to improve ty's type inference or type checking logic should include mdtests
|
||||
demonstrating the effect of the change.
|
||||
|
||||
We write mdtests in a "literate" style, with prose explaining the motivation of each test, and any
|
||||
context necessary to understand the feature being demonstrated.
|
||||
|
||||
### Property tests
|
||||
|
||||
ty uses property-based testing to test the core type relations. These tests are located in
|
||||
[`crates/ty_python_semantic/src/types/property_tests.rs`](../ty_python_semantic/src/types/property_tests.rs).
|
||||
|
||||
The property tests do not run in CI on every PR, just once daily. It is advisable to run them
|
||||
locally after modifying core type relation methods (`is_subtype_of`, `is_equivalent_to`, etc.) to
|
||||
ensure that the changes do not break any of the properties.
|
||||
|
||||
## Ecosystem CI (mypy-primer)
|
||||
|
||||
GitHub Actions will run your changes against a number of real-world projects from GitHub and
|
||||
report on any linter or formatter differences. See [`crates/ty/docs/mypy_primer.md`](./docs/mypy_primer.md)
|
||||
for instructions on running these checks locally.
|
||||
|
||||
## Coding guidelines
|
||||
|
||||
We use the [Salsa](https:://github.com/salsa-rs/salsa) library for incremental computation. Many
|
||||
methods take a Salsa database (usually `db: &'db dyn Db`) as an argument. This should always be the
|
||||
first argument (or second after `self`).
|
||||
|
||||
[mdtest-readme]: ../ty_test/README.md
|
||||
[resources-mdtest]: ../ty_python_semantic/resources/mdtest
|
||||
@@ -28,6 +28,7 @@ colored = { workspace = true }
|
||||
countme = { workspace = true, features = ["enable"] }
|
||||
crossbeam = { workspace = true }
|
||||
ctrlc = { version = "3.4.4" }
|
||||
indicatif = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
# ty
|
||||
|
||||
ty is an extremely fast type checker.
|
||||
Currently, it is a work-in-progress and not ready for user testing.
|
||||
Currently, it is a work-in-progress and not ready for production use.
|
||||
|
||||
ty is designed to prioritize good type inference, even in unannotated code,
|
||||
and aims to avoid false positives.
|
||||
The Rust code for ty lives in this repository; see [CONTRIBUTING.md](CONTRIBUTING.md) for more
|
||||
information on contributing to ty.
|
||||
|
||||
While ty will produce similar results to mypy and pyright on many codebases,
|
||||
100% compatibility with these tools is a non-goal.
|
||||
On some codebases, ty's design decisions lead to different outcomes
|
||||
than you would get from running one of these more established tools.
|
||||
|
||||
## Contributing
|
||||
|
||||
Core type checking tests are written as Markdown code blocks.
|
||||
They can be found in [`ty_python_semantic/resources/mdtest`][resources-mdtest].
|
||||
See [`ty_test/README.md`][mdtest-readme] for more information
|
||||
on the test framework itself.
|
||||
|
||||
The list of open issues can be found [here][open-issues].
|
||||
|
||||
[mdtest-readme]: ../ty_test/README.md
|
||||
[open-issues]: https://github.com/astral-sh/ty/issues
|
||||
[resources-mdtest]: ../ty_python_semantic/resources/mdtest
|
||||
See [the ty repo](https://github.com/astral-sh/ty) for ty documentation and releases.
|
||||
|
||||
@@ -30,7 +30,7 @@ ty check [OPTIONS] [PATH]...
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
||||
<dl class="cli-reference"><dt id="ty-check--paths"><a href="#ty-check--paths"<code>PATHS</code></a></dt><dd><p>List of files or directories to check [default: the project root]</p>
|
||||
<dl class="cli-reference"><dt id="ty-check--paths"><a href="#ty-check--paths"><code>PATHS</code></a></dt><dd><p>List of files or directories to check [default: the project root]</p>
|
||||
</dd></dl>
|
||||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
@@ -56,13 +56,17 @@ ty check [OPTIONS] [PATH]...
|
||||
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
||||
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
|
||||
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
|
||||
</dd><dt id="ty-check--python"><a href="#ty-check--python"><code>--python</code></a> <i>path</i></dt><dd><p>Path to the Python installation from which ty resolves type information and third-party dependencies.</p>
|
||||
<p>If not specified, ty will look at the <code>VIRTUAL_ENV</code> environment variable.</p>
|
||||
<p>ty will search in the path's <code>site-packages</code> directories for type information and third-party imports.</p>
|
||||
<p>This option is commonly used to specify the path to a virtual environment.</p>
|
||||
</dd><dt id="ty-check--python"><a href="#ty-check--python"><code>--python</code></a> <i>path</i></dt><dd><p>Path to the Python environment.</p>
|
||||
<p>ty uses the Python environment to resolve type information and third-party dependencies.</p>
|
||||
<p>If not specified, ty will attempt to infer it from the <code>VIRTUAL_ENV</code> environment variable or discover a <code>.venv</code> directory in the project root or working directory.</p>
|
||||
<p>If a path to a Python interpreter is provided, e.g., <code>.venv/bin/python3</code>, ty will attempt to find an environment two directories up from the interpreter's path, e.g., <code>.venv</code>. At this time, ty does not invoke the interpreter to determine the location of the environment. This means that ty will not resolve dynamic executables such as a shim.</p>
|
||||
<p>ty will search in the resolved environments's <code>site-packages</code> directories for type information and third-party imports.</p>
|
||||
</dd><dt id="ty-check--python-platform"><a href="#ty-check--python-platform"><code>--python-platform</code></a>, <code>--platform</code> <i>platform</i></dt><dd><p>Target platform to assume when resolving types.</p>
|
||||
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
|
||||
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types</p>
|
||||
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types.</p>
|
||||
<p>The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.</p>
|
||||
<p>By default, the Python version is inferred as the lower bound of the project's <code>requires-python</code> field from the <code>pyproject.toml</code>, if available. Otherwise, the latest stable version supported by ty is used, which is currently 3.13.</p>
|
||||
<p>ty will not infer the Python version from the Python environment at this time.</p>
|
||||
<p>Possible values:</p>
|
||||
<ul>
|
||||
<li><code>3.7</code></li>
|
||||
@@ -121,7 +125,7 @@ ty generate-shell-completion <SHELL>
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
||||
<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell"><a href="#ty-generate-shell-completion--shell"<code>SHELL</code></a></dt></dl>
|
||||
<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell"><a href="#ty-generate-shell-completion--shell"><code>SHELL</code></a></dt></dl>
|
||||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Configuration
|
||||
#### [`respect-ignore-files`]
|
||||
#### `respect-ignore-files`
|
||||
|
||||
Whether to automatically exclude files that are ignored by `.ignore`,
|
||||
`.gitignore`, `.git/info/exclude`, and global `gitignore` files.
|
||||
@@ -18,11 +18,11 @@ respect-ignore-files = false
|
||||
|
||||
---
|
||||
|
||||
#### [`rules`]
|
||||
#### `rules`
|
||||
|
||||
Configures the enabled rules and their severity.
|
||||
|
||||
See [the rules documentation](https://github.com/astral-sh/ruff/blob/main/crates/ty/docs/rules.md) for a list of all available rules.
|
||||
See [the rules documentation](https://ty.dev/rules) for a list of all available rules.
|
||||
|
||||
Valid severities are:
|
||||
|
||||
@@ -47,7 +47,7 @@ division-by-zero = "ignore"
|
||||
|
||||
## `environment`
|
||||
|
||||
#### [`extra-paths`]
|
||||
#### `extra-paths`
|
||||
|
||||
List of user-provided paths that should take first priority in the module resolution.
|
||||
Examples in other type checkers are mypy's `MYPYPATH` environment variable,
|
||||
@@ -66,7 +66,7 @@ extra-paths = ["~/shared/my-search-path"]
|
||||
|
||||
---
|
||||
|
||||
#### [`python`]
|
||||
#### `python`
|
||||
|
||||
Path to the Python installation from which ty resolves type information and third-party dependencies.
|
||||
|
||||
@@ -88,7 +88,7 @@ python = "./.venv"
|
||||
|
||||
---
|
||||
|
||||
#### [`python-platform`]
|
||||
#### `python-platform`
|
||||
|
||||
Specifies the target platform that will be used to analyze the source code.
|
||||
If specified, ty will understand conditions based on comparisons with `sys.platform`, such
|
||||
@@ -115,7 +115,7 @@ python-platform = "win32"
|
||||
|
||||
---
|
||||
|
||||
#### [`python-version`]
|
||||
#### `python-version`
|
||||
|
||||
Specifies the version of Python that will be used to analyze the source code.
|
||||
The version should be specified as a string in the format `M.m` where `M` is the major version
|
||||
@@ -139,7 +139,7 @@ python-version = "3.12"
|
||||
|
||||
---
|
||||
|
||||
#### [`typeshed`]
|
||||
#### `typeshed`
|
||||
|
||||
Optional path to a "typeshed" directory on disk for us to use for standard-library types.
|
||||
If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
|
||||
@@ -160,26 +160,26 @@ typeshed = "/path/to/custom/typeshed"
|
||||
|
||||
## `src`
|
||||
|
||||
#### [`root`]
|
||||
#### `root`
|
||||
|
||||
The root(s) of the project, used for finding first-party modules.
|
||||
The root of the project, used for finding first-party modules.
|
||||
|
||||
**Default value**: `[".", "./src"]`
|
||||
|
||||
**Type**: `list[str]`
|
||||
**Type**: `str`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
|
||||
```toml
|
||||
[tool.ty.src]
|
||||
root = ["./app"]
|
||||
root = "./app"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `terminal`
|
||||
|
||||
#### [`error-on-warning`]
|
||||
#### `error-on-warning`
|
||||
|
||||
Use exit code 1 if there are any warning-level diagnostics.
|
||||
|
||||
@@ -199,7 +199,7 @@ error-on-warning = true
|
||||
|
||||
---
|
||||
|
||||
#### [`output-format`]
|
||||
#### `output-format`
|
||||
|
||||
The format to use for printing diagnostic messages.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L84)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L85)
|
||||
</details>
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
@@ -61,11 +61,27 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
<summary>detects when an argument is used as both a value and a type form in a call</summary>
|
||||
|
||||
### What it does
|
||||
Checks whether an argument is used as both a value and a type form in a call
|
||||
Checks whether an argument is used as both a value and a type form in a call.
|
||||
|
||||
### Why is this bad?
|
||||
Such calls have confusing semantics and often indicate a logic error.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
from typing import reveal_type
|
||||
from ty_extensions import is_fully_static
|
||||
|
||||
if flag:
|
||||
f = repr # Expects a value
|
||||
else:
|
||||
f = is_fully_static # Expects a type form
|
||||
|
||||
f(int) # error
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L115)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116)
|
||||
</details>
|
||||
|
||||
## `conflicting-declarations`
|
||||
@@ -83,9 +99,19 @@ A variable with two conflicting declarations likely indicates a mistake.
|
||||
Moreover, it could lead to incorrect or ill-defined type inference for
|
||||
other code that relies on these variables.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
if b:
|
||||
a: int
|
||||
else:
|
||||
a: str
|
||||
|
||||
a = 1
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L125)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142)
|
||||
</details>
|
||||
|
||||
## `conflicting-metaclass`
|
||||
@@ -95,11 +121,28 @@ other code that relies on these variables.
|
||||
<details>
|
||||
<summary>detects conflicting metaclasses</summary>
|
||||
|
||||
TODO #14889
|
||||
### What it does
|
||||
Checks for class definitions where the metaclass of the class
|
||||
being created would not be a subclass of the metaclasses of
|
||||
all the class's bases.
|
||||
|
||||
### Why is it bad?
|
||||
Such a class definition raises a `TypeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class M1(type): ...
|
||||
class M2(type): ...
|
||||
class A(metaclass=M1): ...
|
||||
class B(metaclass=M2): ...
|
||||
|
||||
## TypeError: metaclass conflict
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L140)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L167)
|
||||
</details>
|
||||
|
||||
## `cyclic-class-definition`
|
||||
@@ -110,14 +153,27 @@ TODO #14889
|
||||
<summary>detects cyclic class definitions</summary>
|
||||
|
||||
### What it does
|
||||
Checks for class definitions with a cyclic inheritance chain.
|
||||
Checks for class definitions in stub files that inherit
|
||||
(directly or indirectly) from themselves.
|
||||
|
||||
### Why is it bad?
|
||||
TODO #14889
|
||||
Although forward references are natively supported in stub files,
|
||||
inheritance cycles are still disallowed, as it is impossible to
|
||||
resolve a consistent [method resolution order] for a class that
|
||||
inherits from itself.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
## foo.pyi
|
||||
class A(B): ...
|
||||
class B(A): ...
|
||||
```
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L149)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
@@ -140,7 +196,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219)
|
||||
</details>
|
||||
|
||||
## `duplicate-base`
|
||||
@@ -154,11 +210,19 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Checks for class definitions with duplicate bases.
|
||||
|
||||
### Why is this bad?
|
||||
Class definitions with duplicate bases raise a `TypeError` at runtime.
|
||||
Class definitions with duplicate bases raise `TypeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A: ...
|
||||
|
||||
## TypeError: duplicate base class
|
||||
class B(A, A): ...
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L180)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237)
|
||||
</details>
|
||||
|
||||
## `escape-character-in-forward-annotation`
|
||||
@@ -295,7 +359,7 @@ TypeError: multiple bases have instance lay-out conflict
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258)
|
||||
</details>
|
||||
|
||||
## `inconsistent-mro`
|
||||
@@ -306,14 +370,25 @@ TypeError: multiple bases have instance lay-out conflict
|
||||
<summary>detects class definitions with an inconsistent MRO</summary>
|
||||
|
||||
### What it does
|
||||
Checks for classes with an inconsistent method resolution order (MRO).
|
||||
Checks for classes with an inconsistent [method resolution order] (MRO).
|
||||
|
||||
### Why is this bad?
|
||||
Classes with an inconsistent MRO will raise a `TypeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
## TypeError: Cannot create a consistent method resolution order
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L344)
|
||||
</details>
|
||||
|
||||
## `index-out-of-bounds`
|
||||
@@ -330,9 +405,15 @@ a container.
|
||||
### Why is this bad?
|
||||
Using an out of bounds index will raise an `IndexError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
t = (0, 1, 2)
|
||||
t[3] # IndexError: tuple index out of range
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L292)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L368)
|
||||
</details>
|
||||
|
||||
## `invalid-argument-type`
|
||||
@@ -358,7 +439,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388)
|
||||
</details>
|
||||
|
||||
## `invalid-assignment`
|
||||
@@ -368,11 +449,24 @@ func("foo") # error: [invalid-argument-type]
|
||||
<details>
|
||||
<summary>detects invalid assignments</summary>
|
||||
|
||||
TODO #14889
|
||||
### What it does
|
||||
Checks for assignments where the type of the value
|
||||
is not [assignable to] the type of the assignee.
|
||||
|
||||
### Why is this bad?
|
||||
Such assignments break the rules of the type system and
|
||||
weaken a type checker's ability to accurately reason about your code.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
a: int = ''
|
||||
```
|
||||
|
||||
[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L346)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428)
|
||||
</details>
|
||||
|
||||
## `invalid-attribute-access`
|
||||
@@ -383,20 +477,29 @@ TODO #14889
|
||||
<summary>Invalid attribute access</summary>
|
||||
|
||||
### What it does
|
||||
Makes sure that instance attribute accesses are valid.
|
||||
Checks for assignments to class variables from instances
|
||||
and assignments to instance variables from its class.
|
||||
|
||||
### Why is this bad?
|
||||
Incorrect assignments break the rules of the type system and
|
||||
weaken a type checker's ability to accurately reason about your code.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class C:
|
||||
var: ClassVar[int] = 1
|
||||
class_var: ClassVar[int] = 1
|
||||
instance_var: int
|
||||
|
||||
C.var = 3 # okay
|
||||
C().var = 3 # error: Cannot assign to class variable
|
||||
C.class_var = 3 # okay
|
||||
C().class_var = 3 # error: Cannot assign to class variable
|
||||
|
||||
C().instance_var = 3 # okay
|
||||
C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1099)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
|
||||
</details>
|
||||
|
||||
## `invalid-base`
|
||||
@@ -404,13 +507,13 @@ C().var = 3 # error: Cannot assign to class variable
|
||||
**Default level**: error
|
||||
|
||||
<details>
|
||||
<summary>detects class definitions with an invalid base</summary>
|
||||
<summary>detects invalid bases in class definitions</summary>
|
||||
|
||||
TODO #14889
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L355)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450)
|
||||
</details>
|
||||
|
||||
## `invalid-context-manager`
|
||||
@@ -420,11 +523,23 @@ TODO #14889
|
||||
<details>
|
||||
<summary>detects expressions used in with statements that don't implement the context manager protocol</summary>
|
||||
|
||||
TODO #14889
|
||||
### What it does
|
||||
Checks for expressions used in `with` statements
|
||||
that do not implement the context manager protocol.
|
||||
|
||||
### Why is this bad?
|
||||
Such a statement will raise `TypeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
## TypeError: 'int' object does not support the context manager protocol
|
||||
with 1:
|
||||
print(2)
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L459)
|
||||
</details>
|
||||
|
||||
## `invalid-declaration`
|
||||
@@ -434,11 +549,25 @@ TODO #14889
|
||||
<details>
|
||||
<summary>detects invalid declarations</summary>
|
||||
|
||||
TODO #14889
|
||||
### What it does
|
||||
Checks for declarations where the inferred type of an existing symbol
|
||||
is not [assignable to] its post-hoc declared type.
|
||||
|
||||
### Why is this bad?
|
||||
Such declarations break the rules of the type system and
|
||||
weaken a type checker's ability to accurately reason about your code.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
a = 1
|
||||
a: str
|
||||
```
|
||||
|
||||
[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L373)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L480)
|
||||
</details>
|
||||
|
||||
## `invalid-exception-caught`
|
||||
@@ -479,7 +608,7 @@ except ZeroDivisionError:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L382)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L503)
|
||||
</details>
|
||||
|
||||
## `invalid-generic-class`
|
||||
@@ -510,7 +639,7 @@ class C[U](Generic[T]): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L418)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L539)
|
||||
</details>
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
@@ -543,7 +672,7 @@ def f(t: TypeVar("U")): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L444)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565)
|
||||
</details>
|
||||
|
||||
## `invalid-metaclass`
|
||||
@@ -575,7 +704,7 @@ class B(metaclass=f): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L593)
|
||||
</details>
|
||||
|
||||
## `invalid-overload`
|
||||
@@ -623,7 +752,7 @@ def foo(x: int) -> int: ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L499)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L620)
|
||||
</details>
|
||||
|
||||
## `invalid-parameter-default`
|
||||
@@ -634,14 +763,21 @@ def foo(x: int) -> int: ...
|
||||
<summary>detects default values that can't be assigned to the parameter's annotated type</summary>
|
||||
|
||||
### What it does
|
||||
Checks for default values that can't be assigned to the parameter's annotated type.
|
||||
Checks for default values that can't be
|
||||
assigned to the parameter's annotated type.
|
||||
|
||||
### Why is this bad?
|
||||
TODO #14889
|
||||
This breaks the rules of the type system and
|
||||
weakens a type checker's ability to accurately reason about your code.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
def f(a: int = ''): ...
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663)
|
||||
</details>
|
||||
|
||||
## `invalid-protocol`
|
||||
@@ -674,7 +810,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L251)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316)
|
||||
</details>
|
||||
|
||||
## `invalid-raise`
|
||||
@@ -722,7 +858,7 @@ def g():
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L555)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683)
|
||||
</details>
|
||||
|
||||
## `invalid-return-type`
|
||||
@@ -746,7 +882,7 @@ def func() -> int:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L327)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409)
|
||||
</details>
|
||||
|
||||
## `invalid-super-argument`
|
||||
@@ -790,7 +926,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L598)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726)
|
||||
</details>
|
||||
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
@@ -825,9 +961,15 @@ code seen only by the type checker, and not at runtime. Normally this flag is im
|
||||
must be assigned the value `False` at runtime; the type checker will consider its value to
|
||||
be `True`. If annotated, it must be annotated as a type that can accept `bool` values.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
TYPE_CHECKING: str
|
||||
TYPE_CHECKING = ''
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
|
||||
</details>
|
||||
|
||||
## `invalid-type-form`
|
||||
@@ -838,14 +980,24 @@ be `True`. If annotated, it must be annotated as a type that can accept `bool` v
|
||||
<summary>detects invalid type forms</summary>
|
||||
|
||||
### What it does
|
||||
Checks for invalid type expressions.
|
||||
Checks for expressions that are used as type expressions
|
||||
but cannot validly be interpreted as such.
|
||||
|
||||
### Why is this bad?
|
||||
TODO #14889
|
||||
Such expressions cannot be understood by ty.
|
||||
In some cases, they might raise errors at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
a: type[1] # `1` is not a type
|
||||
b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L789)
|
||||
</details>
|
||||
|
||||
## `invalid-type-variable-constraints`
|
||||
@@ -855,11 +1007,31 @@ TODO #14889
|
||||
<details>
|
||||
<summary>detects invalid type variable constraints</summary>
|
||||
|
||||
TODO #14889
|
||||
### What it does
|
||||
Checks for constrained [type variables] with only one constraint.
|
||||
|
||||
### Why is this bad?
|
||||
A constrained type variable must have at least two constraints.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar('T', str) # invalid constrained TypeVar
|
||||
```
|
||||
|
||||
Use instead:
|
||||
```python
|
||||
T = TypeVar('T', str, int) # valid constrained TypeVar
|
||||
## or
|
||||
T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
```
|
||||
|
||||
[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L668)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812)
|
||||
</details>
|
||||
|
||||
## `missing-argument`
|
||||
@@ -883,7 +1055,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
|
||||
</details>
|
||||
|
||||
## `no-matching-overload`
|
||||
@@ -911,7 +1083,7 @@ func("string") # error: [no-matching-overload]
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L696)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L860)
|
||||
</details>
|
||||
|
||||
## `non-subscriptable`
|
||||
@@ -934,7 +1106,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L719)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L883)
|
||||
</details>
|
||||
|
||||
## `not-iterable`
|
||||
@@ -959,7 +1131,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L737)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901)
|
||||
</details>
|
||||
|
||||
## `parameter-already-assigned`
|
||||
@@ -985,7 +1157,7 @@ f(1, x=2) # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L952)
|
||||
</details>
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
@@ -1028,6 +1200,11 @@ def test(): -> "int":
|
||||
### What it does
|
||||
Makes sure that the argument of `static_assert` is statically known to be true.
|
||||
|
||||
### Why is this bad?
|
||||
A `static_assert` call represents an explicit request from the user
|
||||
for the type checker to emit an error if the argument cannot be verified
|
||||
to evaluate to `True` in a boolean context.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
from ty_extensions import static_assert
|
||||
@@ -1039,7 +1216,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1288)
|
||||
</details>
|
||||
|
||||
## `subclass-of-final-class`
|
||||
@@ -1067,7 +1244,7 @@ class B(A): ... # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L858)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043)
|
||||
</details>
|
||||
|
||||
## `too-many-positional-arguments`
|
||||
@@ -1093,7 +1270,7 @@ f("foo") # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088)
|
||||
</details>
|
||||
|
||||
## `type-assertion-failure`
|
||||
@@ -1120,7 +1297,7 @@ def _(x: int):
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L881)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066)
|
||||
</details>
|
||||
|
||||
## `unavailable-implicit-super-arguments`
|
||||
@@ -1164,7 +1341,7 @@ class A:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1109)
|
||||
</details>
|
||||
|
||||
## `unknown-argument`
|
||||
@@ -1190,7 +1367,7 @@ f(x=1, y=2) # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L979)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166)
|
||||
</details>
|
||||
|
||||
## `unresolved-attribute`
|
||||
@@ -1204,11 +1381,20 @@ f(x=1, y=2) # Error raised here
|
||||
Checks for unresolved attributes.
|
||||
|
||||
### Why is this bad?
|
||||
Accessing an unbound attribute will raise an `AttributeError` at runtime. An unresolved attribute is not guaranteed to exist from the type alone, so this could also indicate that the object is not of the type that the user expects.
|
||||
Accessing an unbound attribute will raise an `AttributeError` at runtime.
|
||||
An unresolved attribute is not guaranteed to exist from the type alone,
|
||||
so this could also indicate that the object is not of the type that the user expects.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A: ...
|
||||
|
||||
A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1000)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187)
|
||||
</details>
|
||||
|
||||
## `unresolved-import`
|
||||
@@ -1222,12 +1408,17 @@ Accessing an unbound attribute will raise an `AttributeError` at runtime. An unr
|
||||
Checks for import statements for which the module cannot be resolved.
|
||||
|
||||
### Why is this bad?
|
||||
Importing a module that cannot be resolved will raise an `ImportError`
|
||||
Importing a module that cannot be resolved will raise a `ModuleNotFoundError`
|
||||
at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
import foo # ModuleNotFoundError: No module named 'foo'
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1013)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1209)
|
||||
</details>
|
||||
|
||||
## `unresolved-reference`
|
||||
@@ -1251,7 +1442,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1027)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1228)
|
||||
</details>
|
||||
|
||||
## `unsupported-bool-conversion`
|
||||
@@ -1287,7 +1478,7 @@ b1 < b2 < b1 # exception raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L757)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L921)
|
||||
</details>
|
||||
|
||||
## `unsupported-operator`
|
||||
@@ -1305,9 +1496,16 @@ the operands don't support the operator.
|
||||
Attempting to use an unsupported operator will raise a `TypeError` at
|
||||
runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A: ...
|
||||
|
||||
A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1046)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1247)
|
||||
</details>
|
||||
|
||||
## `zero-stepsize-in-slice`
|
||||
@@ -1331,7 +1529,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269)
|
||||
</details>
|
||||
|
||||
## `call-possibly-unbound-method`
|
||||
@@ -1349,7 +1547,7 @@ Calling an unbound method will raise an `AttributeError` at runtime.
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-possibly-unbound-method)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L102)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L103)
|
||||
</details>
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
@@ -1394,9 +1592,18 @@ Checks for possibly unbound attributes.
|
||||
### Why is this bad?
|
||||
Attempting to access an unbound attribute will raise an `AttributeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A:
|
||||
if b:
|
||||
c = 0
|
||||
|
||||
A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L973)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-import`
|
||||
@@ -1413,9 +1620,21 @@ Checks for imports of symbols that may be unbound.
|
||||
Importing an unbound module or name will raise a `ModuleNotFoundError`
|
||||
or `ImportError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
## module.py
|
||||
import datetime
|
||||
|
||||
if datetime.date.today().weekday() != 6:
|
||||
a = 1
|
||||
|
||||
## main.py
|
||||
from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L822)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995)
|
||||
</details>
|
||||
|
||||
## `redundant-cast`
|
||||
@@ -1441,7 +1660,7 @@ cast(int, f()) # Redundant
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1118)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1340)
|
||||
</details>
|
||||
|
||||
## `undefined-reveal`
|
||||
@@ -1458,11 +1677,13 @@ Checks for calls to `reveal_type` without importing it.
|
||||
Using `reveal_type` without importing it will raise a `NameError` at runtime.
|
||||
|
||||
### Examples
|
||||
TODO #14889
|
||||
```python
|
||||
reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L963)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148)
|
||||
</details>
|
||||
|
||||
## `unknown-rule`
|
||||
@@ -1519,7 +1740,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L836)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021)
|
||||
</details>
|
||||
|
||||
## `unused-ignore-comment`
|
||||
|
||||
@@ -51,14 +51,20 @@ pub(crate) struct CheckCommand {
|
||||
#[arg(long, value_name = "PROJECT")]
|
||||
pub(crate) project: Option<SystemPathBuf>,
|
||||
|
||||
/// Path to the Python installation from which ty resolves type information and third-party dependencies.
|
||||
/// Path to the Python environment.
|
||||
///
|
||||
/// If not specified, ty will look at the `VIRTUAL_ENV` environment variable.
|
||||
/// ty uses the Python environment to resolve type information and third-party dependencies.
|
||||
///
|
||||
/// ty will search in the path's `site-packages` directories for type information and
|
||||
/// third-party imports.
|
||||
/// If not specified, ty will attempt to infer it from the `VIRTUAL_ENV` environment variable or
|
||||
/// discover a `.venv` directory in the project root or working directory.
|
||||
///
|
||||
/// This option is commonly used to specify the path to a virtual environment.
|
||||
/// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to
|
||||
/// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this
|
||||
/// time, ty does not invoke the interpreter to determine the location of the environment. This
|
||||
/// means that ty will not resolve dynamic executables such as a shim.
|
||||
///
|
||||
/// ty will search in the resolved environments's `site-packages` directories for type
|
||||
/// information and third-party imports.
|
||||
#[arg(long, value_name = "PATH")]
|
||||
pub(crate) python: Option<SystemPathBuf>,
|
||||
|
||||
@@ -71,6 +77,15 @@ pub(crate) struct CheckCommand {
|
||||
pub(crate) extra_search_path: Option<Vec<SystemPathBuf>>,
|
||||
|
||||
/// Python version to assume when resolving types.
|
||||
///
|
||||
/// The Python version affects allowed syntax, type definitions of the standard library, and
|
||||
/// type definitions of first- and third-party modules that are conditional on the Python version.
|
||||
///
|
||||
/// By default, the Python version is inferred as the lower bound of the project's
|
||||
/// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest
|
||||
/// stable version supported by ty is used, which is currently 3.13.
|
||||
///
|
||||
/// ty will not infer the Python version from the Python environment at this time.
|
||||
#[arg(long, value_name = "VERSION", alias = "target-version")]
|
||||
pub(crate) python_version: Option<PythonVersion>,
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use ruff_db::Upcast;
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use ty_project::metadata::options::Options;
|
||||
use ty_project::watch::ProjectWatcher;
|
||||
use ty_project::{watch, Db};
|
||||
use ty_project::{watch, Db, DummyReporter, Reporter};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_server::run_server;
|
||||
|
||||
@@ -62,6 +62,11 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
countme::enable(verbosity.is_trace());
|
||||
let _guard = setup_tracing(verbosity)?;
|
||||
|
||||
tracing::warn!(
|
||||
"ty is pre-release software and not ready for production use. \
|
||||
Expect to encounter bugs, missing features, and fatal errors.",
|
||||
);
|
||||
|
||||
tracing::debug!("Version: {}", version::version());
|
||||
|
||||
// The base path to which all CLI arguments are relative to.
|
||||
@@ -200,22 +205,34 @@ impl MainLoop {
|
||||
|
||||
self.watcher = Some(ProjectWatcher::new(watcher, db));
|
||||
|
||||
self.run(db)?;
|
||||
// Do not show progress bars with `--watch`, indicatif does not seem to
|
||||
// handle cancelling independent progress bars very well.
|
||||
self.run_with_progress::<DummyReporter>(db)?;
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
fn run(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
fn run(self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
self.run_with_progress::<IndicatifReporter>(db)
|
||||
}
|
||||
|
||||
fn run_with_progress<R>(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
||||
|
||||
let result = self.main_loop(db);
|
||||
let result = self.main_loop::<R>(db);
|
||||
|
||||
tracing::debug!("Exiting main loop");
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
fn main_loop<R>(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
// Schedule the first check.
|
||||
tracing::debug!("Starting main loop");
|
||||
|
||||
@@ -226,11 +243,12 @@ impl MainLoop {
|
||||
MainLoopMessage::CheckWorkspace => {
|
||||
let db = db.clone();
|
||||
let sender = self.sender.clone();
|
||||
let mut reporter = R::default();
|
||||
|
||||
// Spawn a new task that checks the project. This needs to be done in a separate thread
|
||||
// to prevent blocking the main loop here.
|
||||
rayon::spawn(move || {
|
||||
match db.check() {
|
||||
match db.check_with_reporter(&mut reporter) {
|
||||
Ok(result) => {
|
||||
// Send the result back to the main loop for printing.
|
||||
sender
|
||||
@@ -340,6 +358,32 @@ impl MainLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// A progress reporter for `ty check`.
|
||||
#[derive(Default)]
|
||||
struct IndicatifReporter(Option<indicatif::ProgressBar>);
|
||||
|
||||
impl ty_project::Reporter for IndicatifReporter {
|
||||
fn set_files(&mut self, files: usize) {
|
||||
let progress = indicatif::ProgressBar::new(files as u64);
|
||||
progress.set_style(
|
||||
indicatif::ProgressStyle::with_template(
|
||||
"{msg:8.dim} {bar:60.green/dim} {pos}/{len} files",
|
||||
)
|
||||
.unwrap()
|
||||
.progress_chars("--"),
|
||||
);
|
||||
progress.set_message("Checking");
|
||||
|
||||
self.0 = Some(progress);
|
||||
}
|
||||
|
||||
fn report_file(&self, _file: &ruff_db::files::File) {
|
||||
if let Some(ref progress_bar) = self.0 {
|
||||
progress_bar.inc(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MainLoopCancellationToken {
|
||||
sender: crossbeam_channel::Sender<MainLoopMessage>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user