Compare commits

..

3 Commits

Author SHA1 Message Date
Micha Reiser
05854142e7 Test Salsa #851 2025-05-08 10:53:51 +02:00
David Peter
33eb008fb6 Disabled event handler if tracing is not enabled 2025-05-08 10:28:59 +02:00
David Peter
04d4febfc4 [ty] Update salsa 2025-05-08 10:17:32 +02:00
350 changed files with 3613 additions and 10859 deletions

View File

@@ -1,7 +0,0 @@
#:schema ../ty.schema.json
# Configuration overrides for the mypy primer run
# Enable off-by-default rules.
[rules]
possibly-unresolved-reference = "warn"
unused-ignore-comment = "warn"

View File

@@ -40,7 +40,7 @@ jobs:
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
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.16.0
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
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.4.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
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.4.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
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.16.0
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
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.4.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@@ -239,11 +239,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # 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@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # 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@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # 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@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # 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@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
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@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
- uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
- 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@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
with:
tool: cargo-codspeed

View File

@@ -50,10 +50,6 @@ jobs:
run: |
cd ruff
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
mkdir -p ~/.config/ty
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
echo "new commit"

View File

@@ -5,7 +5,6 @@ exclude: |
.github/workflows/release.yml|
crates/ty_vendored/vendor/.*|
crates/ty_project/resources/.*|
crates/ty/docs/(configuration|rules|cli).md|
crates/ruff_benchmark/resources/.*|
crates/ruff_linter/resources/.*|
crates/ruff_linter/src/rules/.*/snapshots/.*|
@@ -80,7 +79,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.9
rev: v0.11.8
hooks:
- id: ruff-format
- id: ruff
@@ -98,7 +97,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.7.0
rev: v1.6.0
hooks:
- id: zizmor

View File

@@ -1,37 +1,5 @@
# Changelog
## 0.11.9
### Preview features
- Default to latest supported Python version for version-related syntax errors ([#17529](https://github.com/astral-sh/ruff/pull/17529))
- Implement deferred annotations for Python 3.14 ([#17658](https://github.com/astral-sh/ruff/pull/17658))
- \[`airflow`\] Fix `SQLTableCheckOperator` typo (`AIR302`) ([#17946](https://github.com/astral-sh/ruff/pull/17946))
- \[`airflow`\] Remove `airflow.utils.dag_parsing_context.get_parsing_context` (`AIR301`) ([#17852](https://github.com/astral-sh/ruff/pull/17852))
- \[`airflow`\] Skip attribute check in try catch block (`AIR301`) ([#17790](https://github.com/astral-sh/ruff/pull/17790))
- \[`flake8-bandit`\] Mark tuples of string literals as trusted input in `S603` ([#17801](https://github.com/astral-sh/ruff/pull/17801))
- \[`isort`\] Check full module path against project root(s) when categorizing first-party imports ([#16565](https://github.com/astral-sh/ruff/pull/16565))
- \[`ruff`\] Add new rule `in-empty-collection` (`RUF060`) ([#16480](https://github.com/astral-sh/ruff/pull/16480))
### Bug fixes
- Fix missing `combine` call for `lint.typing-extensions` setting ([#17823](https://github.com/astral-sh/ruff/pull/17823))
- \[`flake8-async`\] Fix module name in `ASYNC110`, `ASYNC115`, and `ASYNC116` fixes ([#17774](https://github.com/astral-sh/ruff/pull/17774))
- \[`pyupgrade`\] Add spaces between tokens as necessary to avoid syntax errors in `UP018` autofix ([#17648](https://github.com/astral-sh/ruff/pull/17648))
- \[`refurb`\] Fix false positive for float and complex numbers in `FURB116` ([#17661](https://github.com/astral-sh/ruff/pull/17661))
- [parser] Flag single unparenthesized generator expr with trailing comma in arguments. ([#17893](https://github.com/astral-sh/ruff/pull/17893))
### Documentation
- Add instructions on how to upgrade to a newer Rust version ([#17928](https://github.com/astral-sh/ruff/pull/17928))
- Update code of conduct email address ([#17875](https://github.com/astral-sh/ruff/pull/17875))
- Add fix safety sections to `PLC2801`, `PLR1722`, and `RUF013` ([#17825](https://github.com/astral-sh/ruff/pull/17825), [#17826](https://github.com/astral-sh/ruff/pull/17826), [#17759](https://github.com/astral-sh/ruff/pull/17759))
- Add link to `check-typed-exception` from `S110` and `S112` ([#17786](https://github.com/astral-sh/ruff/pull/17786))
### Other changes
- Allow passing a virtual environment to `ruff analyze graph` ([#17743](https://github.com/astral-sh/ruff/pull/17743))
## 0.11.8
### Preview features

105
Cargo.lock generated
View File

@@ -334,9 +334,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.38"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
"clap_derive",
@@ -344,9 +344,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.38"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
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 0.29.0",
"nix",
"terminfo",
"thiserror 2.0.12",
"which",
@@ -681,11 +681,11 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.4.7"
version = "3.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
dependencies = [
"nix 0.30.1",
"nix",
"windows-sys 0.59.0",
]
@@ -904,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1057,9 +1057,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"js-sys",
@@ -1485,7 +1485,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.0",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1539,9 +1539,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.13"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
@@ -1549,14 +1549,14 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "jiff-static"
version = "0.2.13"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300"
dependencies = [
"proc-macro2",
"quote",
@@ -1771,15 +1771,6 @@ dependencies = [
"url",
]
[[package]]
name = "markdown"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
dependencies = [
"unicode-id",
]
[[package]]
name = "matchers"
version = "0.1.0"
@@ -1880,18 +1871,6 @@ 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"
@@ -2470,7 +2449,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.2",
]
[[package]]
@@ -2570,7 +2549,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.11.9"
version = "0.11.8"
dependencies = [
"anyhow",
"argfile",
@@ -2604,7 +2583,6 @@ dependencies = [
"ruff_linter",
"ruff_macros",
"ruff_notebook",
"ruff_options_metadata",
"ruff_python_ast",
"ruff_python_formatter",
"ruff_python_parser",
@@ -2723,7 +2701,6 @@ dependencies = [
"indoc",
"itertools 0.14.0",
"libcst",
"markdown",
"pretty_assertions",
"rayon",
"regex",
@@ -2732,7 +2709,6 @@ dependencies = [
"ruff_formatter",
"ruff_linter",
"ruff_notebook",
"ruff_options_metadata",
"ruff_python_ast",
"ruff_python_codegen",
"ruff_python_formatter",
@@ -2749,9 +2725,7 @@ dependencies = [
"tracing",
"tracing-indicatif",
"tracing-subscriber",
"ty",
"ty_project",
"url",
]
[[package]]
@@ -2811,7 +2785,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.11.9"
version = "0.11.8"
dependencies = [
"aho-corasick",
"anyhow",
@@ -2839,7 +2813,6 @@ dependencies = [
"regex",
"ruff_annotate_snippets",
"ruff_cache",
"ruff_db",
"ruff_diagnostics",
"ruff_macros",
"ruff_notebook",
@@ -2901,13 +2874,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "ruff_options_metadata"
version = "0.0.0"
dependencies = [
"serde",
]
[[package]]
name = "ruff_python_ast"
version = "0.0.0"
@@ -3146,11 +3112,11 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.11.9"
version = "0.11.8"
dependencies = [
"console_error_panic_hook",
"console_log",
"getrandom 0.3.3",
"getrandom 0.3.2",
"js-sys",
"log",
"ruff_formatter",
@@ -3194,7 +3160,6 @@ dependencies = [
"ruff_graph",
"ruff_linter",
"ruff_macros",
"ruff_options_metadata",
"ruff_python_ast",
"ruff_python_formatter",
"ruff_python_semantic",
@@ -3241,7 +3206,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -3254,7 +3219,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.9.3",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -3272,7 +3237,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
dependencies = [
"boxcar",
"compact_str",
@@ -3295,12 +3260,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
[[package]]
name = "salsa-macros"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
dependencies = [
"heck",
"proc-macro2",
@@ -3637,10 +3602,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"getrandom 0.3.2",
"once_cell",
"rustix 1.0.2",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -3996,7 +3961,6 @@ dependencies = [
"crossbeam",
"ctrlc",
"filetime",
"indicatif",
"insta",
"insta-cmd",
"jiff",
@@ -4049,7 +4013,6 @@ dependencies = [
"ruff_cache",
"ruff_db",
"ruff_macros",
"ruff_options_metadata",
"ruff_python_ast",
"ruff_python_formatter",
"ruff_text_size",
@@ -4180,7 +4143,7 @@ version = "0.0.0"
dependencies = [
"console_error_panic_hook",
"console_log",
"getrandom 0.3.3",
"getrandom 0.3.2",
"js-sys",
"log",
"ruff_db",
@@ -4256,12 +4219,6 @@ dependencies = [
"unic-common",
]
[[package]]
name = "unicode-id"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@@ -4365,7 +4322,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.2",
"js-sys",
"rand 0.9.1",
"uuid-macro-internal",

View File

@@ -23,7 +23,6 @@ ruff_index = { path = "crates/ruff_index" }
ruff_linter = { path = "crates/ruff_linter" }
ruff_macros = { path = "crates/ruff_macros" }
ruff_notebook = { path = "crates/ruff_notebook" }
ruff_options_metadata = { path = "crates/ruff_options_metadata" }
ruff_python_ast = { path = "crates/ruff_python_ast" }
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
@@ -38,7 +37,6 @@ ruff_source_file = { path = "crates/ruff_source_file" }
ruff_text_size = { path = "crates/ruff_text_size" }
ruff_workspace = { path = "crates/ruff_workspace" }
ty = { path = "crates/ty" }
ty_ide = { path = "crates/ty_ide" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
@@ -126,7 +124,7 @@ rayon = { version = "1.10.0" }
regex = { version = "1.10.2" }
rustc-hash = { version = "2.0.0" }
# 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" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef93d3830ffb8dc621fef1ba7b234ccef9dc3639" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
@@ -184,7 +182,7 @@ wild = { version = "2" }
zip = { version = "0.6.6", default-features = false }
[workspace.metadata.cargo-shear]
ignored = ["getrandom", "ruff_options_metadata"]
ignored = ["getrandom"]
[workspace.lints.rust]

View File

@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.11.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.11.9/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.11.8/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.11.8/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.9
rev: v0.11.8
hooks:
# Run the linter.
- id: ruff

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.11.9"
version = "0.11.8"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -20,7 +20,6 @@ ruff_graph = { workspace = true, features = ["serde", "clap"] }
ruff_linter = { workspace = true, features = ["clap"] }
ruff_macros = { workspace = true }
ruff_notebook = { workspace = true }
ruff_options_metadata = { workspace = true, features = ["serde"] }
ruff_python_ast = { workspace = true }
ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true }
@@ -84,7 +83,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"), not(target_os = "android"), 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"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[lints]

View File

@@ -22,12 +22,12 @@ use ruff_linter::settings::types::{
PythonVersion, UnsafeFixes,
};
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
use ruff_options_metadata::{OptionEntry, OptionsMetadata};
use ruff_python_ast as ast;
use ruff_source_file::{LineIndex, OneIndexed, PositionEncoding};
use ruff_text_size::TextRange;
use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::options::{Options, PycodestyleOptions};
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
use ruff_workspace::resolver::ConfigurationTransformer;
use rustc_hash::FxHashMap;
use toml;

View File

@@ -439,7 +439,7 @@ impl LintCacheData {
.map(|msg| {
// Make sure that all message use the same source file.
assert_eq!(
msg.file,
&msg.file,
messages.first().unwrap().source_file(),
"message uses a different source file"
);

View File

@@ -2,8 +2,10 @@ use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use itertools::Itertools;
use std::str::FromStr;
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
use ruff_workspace::options::Options;
use ruff_workspace::{
options::Options,
options_base::{OptionField, OptionSet, OptionsMetadata, Visit},
};
#[derive(Default)]
struct CollectOptionsVisitor {

View File

@@ -2,8 +2,8 @@ use anyhow::{anyhow, Result};
use crate::args::HelpFormat;
use ruff_options_metadata::OptionsMetadata;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::OptionsMetadata;
#[expect(clippy::print_stdout)]
pub(crate) fn config(key: Option<&str>, format: HelpFormat) -> Result<()> {

View File

@@ -15,7 +15,7 @@ use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
use ruff_linter::codes::Rule;
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
use ruff_linter::message::Message;
use ruff_linter::message::{Message, SyntaxErrorMessage};
use ruff_linter::package::PackageRoot;
use ruff_linter::pyproject_toml::lint_pyproject_toml;
use ruff_linter::settings::types::UnsafeFixes;
@@ -102,7 +102,11 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let dummy = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![Message::syntax_error(err, TextRange::default(), dummy)],
vec![Message::SyntaxError(SyntaxErrorMessage {
message: err.to_string(),
range: TextRange::default(),
file: dummy,
})],
FxHashMap::default(),
)
}

View File

@@ -15,7 +15,6 @@ 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",

View File

@@ -59,40 +59,13 @@ type KeyDiagnosticFields = (
Severity,
);
// left: [
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to function `skip_until` is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to function `skip_until` is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to function `skip_until` is incorrect", Error),
// ]
//right: [
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to this function is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to this function is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to this function is incorrect", Error),
// ]
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(8224..8254),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(16914..16948),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(17319..17363),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
];
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[(
DiagnosticId::lint("unused-ignore-comment"),
Some("/src/tomllib/_parser.py"),
Some(22299..22333),
"Unused blanket `type: ignore` directive",
Severity::Warning,
)];
fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name())
@@ -230,7 +203,7 @@ fn assert_diagnostics(db: &dyn Db, diagnostics: &[Diagnostic], expected: &[KeyDi
diagnostic.id(),
diagnostic
.primary_span()
.map(|span| span.expect_ty_file())
.map(|span| span.file())
.map(|file| file.path(db).as_str()),
diagnostic
.primary_span()

View File

@@ -1,14 +1,15 @@
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};
pub use self::render::DisplayDiagnostic;
use crate::{files::File, Db};
use crate::files::File;
use crate::Db;
use self::render::FileResolver;
mod render;
mod stylesheet;
@@ -114,9 +115,10 @@ impl Diagnostic {
/// callers should prefer using this with `write!` instead of `writeln!`.
pub fn display<'a>(
&'a self,
resolver: &'a dyn FileResolver,
db: &'a dyn Db,
config: &'a DisplayDiagnosticConfig,
) -> DisplayDiagnostic<'a> {
let resolver = FileResolver::new(db);
DisplayDiagnostic::new(resolver, config, self)
}
@@ -231,16 +233,6 @@ impl Diagnostic {
self.primary_annotation().map(|ann| ann.tags.as_slice())
}
/// Returns the "primary" span of this diagnostic, panicking if it does not exist.
///
/// This should typically only be used when working with diagnostics in Ruff, where diagnostics
/// are currently required to have a primary span.
///
/// See [`Diagnostic::primary_span`] for more details.
pub fn expect_primary_span(&self) -> Span {
self.primary_span().expect("Expected a primary span")
}
/// Returns a key that can be used to sort two diagnostics into the canonical order
/// in which they should appear when rendered.
pub fn rendering_sort_key<'a>(&'a self, db: &'a dyn Db) -> impl Ord + 'a {
@@ -275,7 +267,11 @@ impl Ord for RenderingSortKey<'_> {
self.diagnostic.primary_span(),
other.diagnostic.primary_span(),
) {
let order = span1.file().path(&self.db).cmp(span2.file().path(&self.db));
let order = span1
.file()
.path(self.db)
.as_str()
.cmp(span2.file().path(self.db).as_str());
if order.is_ne() {
return order;
}
@@ -612,84 +608,62 @@ impl DiagnosticId {
code.split_once(':').map(|(_, rest)| rest)
}
/// Returns a concise description of this diagnostic ID.
/// Returns `true` if this `DiagnosticId` matches the given name.
///
/// 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) => name.as_str(),
DiagnosticId::RevealedType => "revealed-type",
DiagnosticId::UnknownRule => "unknown-rule",
/// ## 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 is_invalid_syntax(&self) -> bool {
matches!(self, Self::InvalidSyntax)
pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> {
Ok(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::RevealedType => "revealed-type",
DiagnosticId::UnknownRule => "unknown-rule",
})
}
}
#[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 {
write!(f, "{}", self.as_str())
}
}
/// A unified file representation for both ruff and ty.
///
/// Such a representation is needed for rendering [`Diagnostic`]s that can optionally contain
/// [`Annotation`]s with [`Span`]s that need to refer to the text of a file. However, ty and ruff
/// use very different file types: a `Copy`-able salsa-interned [`File`], and a heavier-weight
/// [`SourceFile`], respectively.
///
/// This enum presents a unified interface to these two types for the sake of creating [`Span`]s and
/// emitting diagnostics from both ty and ruff.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnifiedFile {
Ty(File),
Ruff(SourceFile),
}
impl UnifiedFile {
pub fn path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a str {
match self {
UnifiedFile::Ty(file) => resolver.path(*file),
UnifiedFile::Ruff(file) => file.name(),
}
}
fn diagnostic_source(&self, resolver: &dyn FileResolver) -> DiagnosticSource {
match self {
UnifiedFile::Ty(file) => DiagnosticSource::Ty(resolver.input(*file)),
UnifiedFile::Ruff(file) => DiagnosticSource::Ruff(file.clone()),
}
}
}
/// A unified wrapper for types that can be converted to a [`SourceCode`].
///
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
/// [`DiagnosticSource`] wraps both of these and provides the single
/// [`DiagnosticSource::as_source_code`] method to produce a [`SourceCode`] with the appropriate
/// lifetimes.
///
/// See [`UnifiedFile::diagnostic_source`] for a way to obtain a [`DiagnosticSource`] from a file
/// and [`FileResolver`].
#[derive(Clone, Debug)]
enum DiagnosticSource {
Ty(Input),
Ruff(SourceFile),
}
impl DiagnosticSource {
/// Returns this input as a `SourceCode` for convenient querying.
fn as_source_code(&self) -> SourceCode {
match self {
DiagnosticSource::Ty(input) => SourceCode::new(input.text.as_str(), &input.line_index),
DiagnosticSource::Ruff(source) => SourceCode::new(source.source_text(), source.index()),
match self.as_str() {
Ok(name) => f.write_str(name),
Err(DiagnosticAsStrError::Category { category, name }) => {
write!(f, "{category}:{name}")
}
}
}
}
@@ -701,14 +675,14 @@ impl DiagnosticSource {
/// the entire file. For example, when the file should be executable but isn't.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
file: UnifiedFile,
file: File,
range: Option<TextRange>,
}
impl Span {
/// Returns the `UnifiedFile` attached to this `Span`.
pub fn file(&self) -> &UnifiedFile {
&self.file
/// Returns the `File` attached to this `Span`.
pub fn file(&self) -> File {
self.file
}
/// Returns the range, if available, attached to this `Span`.
@@ -729,38 +703,10 @@ impl Span {
pub fn with_optional_range(self, range: Option<TextRange>) -> Span {
Span { range, ..self }
}
/// Returns the [`File`] attached to this [`Span`].
///
/// Panics if the file is a [`UnifiedFile::Ruff`] instead of a [`UnifiedFile::Ty`].
pub fn expect_ty_file(&self) -> File {
match self.file {
UnifiedFile::Ty(file) => file,
UnifiedFile::Ruff(_) => panic!("Expected a ty `File`, found a ruff `SourceFile`"),
}
}
/// Returns the [`SourceFile`] attached to this [`Span`].
///
/// Panics if the file is a [`UnifiedFile::Ty`] instead of a [`UnifiedFile::Ruff`].
pub fn expect_ruff_file(&self) -> &SourceFile {
match &self.file {
UnifiedFile::Ty(_) => panic!("Expected a ruff `SourceFile`, found a ty `File`"),
UnifiedFile::Ruff(file) => file,
}
}
}
impl From<File> for Span {
fn from(file: File) -> Span {
let file = UnifiedFile::Ty(file);
Span { file, range: None }
}
}
impl From<SourceFile> for Span {
fn from(file: SourceFile) -> Self {
let file = UnifiedFile::Ruff(file);
Span { file, range: None }
}
}

View File

@@ -16,8 +16,7 @@ use crate::{
};
use super::{
Annotation, Diagnostic, DiagnosticFormat, DiagnosticSource, DisplayDiagnosticConfig, Severity,
SubDiagnostic,
Annotation, Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, Severity, SubDiagnostic,
};
/// A type that implements `std::fmt::Display` for diagnostic rendering.
@@ -31,16 +30,17 @@ use super::{
/// values. When using Salsa, this most commonly corresponds to the lifetime
/// of a Salsa `Db`.
/// * The lifetime of the diagnostic being rendered.
#[derive(Debug)]
pub struct DisplayDiagnostic<'a> {
config: &'a DisplayDiagnosticConfig,
resolver: &'a dyn FileResolver,
resolver: FileResolver<'a>,
annotate_renderer: AnnotateRenderer,
diag: &'a Diagnostic,
}
impl<'a> DisplayDiagnostic<'a> {
pub(crate) fn new(
resolver: &'a dyn FileResolver,
resolver: FileResolver<'a>,
config: &'a DisplayDiagnosticConfig,
diag: &'a Diagnostic,
) -> DisplayDiagnostic<'a> {
@@ -86,13 +86,11 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
write!(
f,
" {path}",
path = fmt_styled(span.file().path(self.resolver), stylesheet.emphasis)
path = fmt_styled(self.resolver.path(span.file()), stylesheet.emphasis)
)?;
if let Some(range) = span.range() {
let diagnostic_source = span.file().diagnostic_source(self.resolver);
let start = diagnostic_source
.as_source_code()
.line_column(range.start());
let input = self.resolver.input(span.file());
let start = input.as_source_code().line_column(range.start());
write!(
f,
@@ -117,7 +115,7 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
.emphasis(stylesheet.emphasis)
.none(stylesheet.none);
let resolved = Resolved::new(self.resolver, self.diag);
let resolved = Resolved::new(&self.resolver, self.diag);
let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
@@ -140,23 +138,26 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
/// both.)
#[derive(Debug)]
struct Resolved<'a> {
id: String,
diagnostics: Vec<ResolvedDiagnostic<'a>>,
}
impl<'a> Resolved<'a> {
/// Creates a new resolved set of diagnostics.
fn new(resolver: &'a dyn FileResolver, diag: &'a Diagnostic) -> Resolved<'a> {
fn new(resolver: &FileResolver<'a>, diag: &'a Diagnostic) -> Resolved<'a> {
let mut diagnostics = vec![];
diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, diag));
for sub in &diag.inner.subs {
diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub));
}
Resolved { diagnostics }
let id = diag.inner.id.to_string();
Resolved { id, 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()
@@ -174,7 +175,6 @@ impl<'a> Resolved<'a> {
#[derive(Debug)]
struct ResolvedDiagnostic<'a> {
severity: Severity,
id: Option<String>,
message: String,
annotations: Vec<ResolvedAnnotation<'a>>,
}
@@ -182,7 +182,7 @@ struct ResolvedDiagnostic<'a> {
impl<'a> ResolvedDiagnostic<'a> {
/// Resolve a single diagnostic.
fn from_diagnostic(
resolver: &'a dyn FileResolver,
resolver: &FileResolver<'a>,
diag: &'a Diagnostic,
) -> ResolvedDiagnostic<'a> {
let annotations: Vec<_> = diag
@@ -190,16 +190,25 @@ impl<'a> ResolvedDiagnostic<'a> {
.annotations
.iter()
.filter_map(|ann| {
let path = ann.span.file.path(resolver);
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
ResolvedAnnotation::new(path, &diagnostic_source, ann)
let path = resolver.path(ann.span.file);
let input = resolver.input(ann.span.file);
ResolvedAnnotation::new(path, &input, ann)
})
.collect();
let id = Some(diag.inner.id.to_string());
let message = diag.inner.message.as_str().to_string();
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(),
)
};
ResolvedDiagnostic {
severity: diag.inner.severity,
id,
message,
annotations,
}
@@ -207,7 +216,7 @@ impl<'a> ResolvedDiagnostic<'a> {
/// Resolve a single sub-diagnostic.
fn from_sub_diagnostic(
resolver: &'a dyn FileResolver,
resolver: &FileResolver<'a>,
diag: &'a SubDiagnostic,
) -> ResolvedDiagnostic<'a> {
let annotations: Vec<_> = diag
@@ -215,14 +224,13 @@ impl<'a> ResolvedDiagnostic<'a> {
.annotations
.iter()
.filter_map(|ann| {
let path = ann.span.file.path(resolver);
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
ResolvedAnnotation::new(path, &diagnostic_source, ann)
let path = resolver.path(ann.span.file);
let input = resolver.input(ann.span.file);
ResolvedAnnotation::new(path, &input, ann)
})
.collect();
ResolvedDiagnostic {
severity: diag.inner.severity,
id: None,
message: diag.inner.message.as_str().to_string(),
annotations,
}
@@ -251,18 +259,10 @@ impl<'a> ResolvedDiagnostic<'a> {
continue;
};
let prev_context_ends = context_after(
&prev.diagnostic_source.as_source_code(),
context,
prev.line_end,
)
.get();
let this_context_begins = context_before(
&ann.diagnostic_source.as_source_code(),
context,
ann.line_start,
)
.get();
let prev_context_ends =
context_after(&prev.input.as_source_code(), context, prev.line_end).get();
let this_context_begins =
context_before(&ann.input.as_source_code(), context, ann.line_start).get();
// The boundary case here is when `prev_context_ends`
// is exactly one less than `this_context_begins`. In
// that case, the context windows are adajcent and we
@@ -289,7 +289,6 @@ 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,
}
@@ -305,7 +304,7 @@ impl<'a> ResolvedDiagnostic<'a> {
#[derive(Debug)]
struct ResolvedAnnotation<'a> {
path: &'a str,
diagnostic_source: DiagnosticSource,
input: Input,
range: TextRange,
line_start: OneIndexed,
line_end: OneIndexed,
@@ -319,12 +318,8 @@ impl<'a> ResolvedAnnotation<'a> {
/// `path` is the path of the file that this annotation points to.
///
/// `input` is the contents of the file that this annotation points to.
fn new(
path: &'a str,
diagnostic_source: &DiagnosticSource,
ann: &'a Annotation,
) -> Option<ResolvedAnnotation<'a>> {
let source = diagnostic_source.as_source_code();
fn new(path: &'a str, input: &Input, ann: &'a Annotation) -> Option<ResolvedAnnotation<'a>> {
let source = input.as_source_code();
let (range, line_start, line_end) = match (ann.span.range(), ann.message.is_some()) {
// An annotation with no range AND no message is probably(?)
// meaningless, but we should try to render it anyway.
@@ -350,7 +345,7 @@ impl<'a> ResolvedAnnotation<'a> {
};
Some(ResolvedAnnotation {
path,
diagnostic_source: diagnostic_source.clone(),
input: input.clone(),
range,
line_start,
line_end,
@@ -369,6 +364,20 @@ 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>>,
}
@@ -377,12 +386,6 @@ 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,
@@ -403,11 +406,7 @@ impl RenderableDiagnostic<'_> {
.iter()
.map(|snippet| snippet.to_annotate(path))
});
let mut message = level.title(self.message);
if let Some(id) = self.id {
message = message.id(id);
}
message.snippets(snippets)
level.title(self.message).snippets(snippets)
}
}
@@ -511,8 +510,8 @@ impl<'r> RenderableSnippet<'r> {
!anns.is_empty(),
"creating a renderable snippet requires a non-zero number of annotations",
);
let diagnostic_source = &anns[0].diagnostic_source;
let source = diagnostic_source.as_source_code();
let input = &anns[0].input;
let source = input.as_source_code();
let has_primary = anns.iter().any(|ann| ann.is_primary);
let line_start = context_before(
@@ -528,7 +527,7 @@ impl<'r> RenderableSnippet<'r> {
let snippet_start = source.line_start(line_start);
let snippet_end = source.line_end(line_end);
let snippet = diagnostic_source
let snippet = input
.as_source_code()
.slice(TextRange::new(snippet_start, snippet_end));
@@ -614,7 +613,7 @@ impl<'r> RenderableAnnotation<'r> {
}
}
/// A trait that facilitates the retrieval of source code from a `Span`.
/// A type that facilitates the retrieval of source code from a `Span`.
///
/// At present, this is tightly coupled with a Salsa database. In the future,
/// it is intended for this resolver to become an abstraction providing a
@@ -629,36 +628,55 @@ impl<'r> RenderableAnnotation<'r> {
/// callers will need to pass in a different "resolver" for turning `Span`s
/// into actual file paths/contents. The infrastructure for this isn't fully in
/// place, but this type serves to demarcate the intended abstraction boundary.
pub trait FileResolver {
/// Returns the path associated with the file given.
fn path(&self, file: File) -> &str;
/// Returns the input contents associated with the file given.
fn input(&self, file: File) -> Input;
pub(crate) struct FileResolver<'a> {
db: &'a dyn Db,
}
impl FileResolver for &dyn Db {
fn path(&self, file: File) -> &str {
relativize_path(self.system().current_directory(), file.path(*self).as_str())
impl<'a> FileResolver<'a> {
/// Creates a new resolver from a Salsa database.
pub(crate) fn new(db: &'a dyn Db) -> FileResolver<'a> {
FileResolver { db }
}
/// Returns the path associated with the file given.
fn path(&self, file: File) -> &'a str {
relativize_path(
self.db.system().current_directory(),
file.path(self.db).as_str(),
)
}
/// Returns the input contents associated with the file given.
fn input(&self, file: File) -> Input {
Input {
text: source_text(*self, file),
line_index: line_index(*self, file),
text: source_text(self.db, file),
line_index: line_index(self.db, file),
}
}
}
impl std::fmt::Debug for FileResolver<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "<salsa based file resolver>")
}
}
/// An abstraction over a unit of user input.
///
/// A single unit of user input usually corresponds to a `File`.
/// This contains the actual content of that input as well as a
/// line index for efficiently querying its contents.
#[derive(Clone, Debug)]
pub struct Input {
pub(crate) text: SourceText,
pub(crate) line_index: LineIndex,
struct Input {
text: SourceText,
line_index: LineIndex,
}
impl Input {
/// Returns this input as a `SourceCode` for convenient querying.
fn as_source_code(&self) -> SourceCode<'_, '_> {
SourceCode::new(self.text.as_str(), &self.line_index)
}
}
/// Returns the line number accounting for the given `len`
@@ -712,7 +730,6 @@ mod tests {
use crate::files::system_path_to_file;
use crate::system::{DbWithWritableSystem, SystemPath};
use crate::tests::TestDb;
use crate::Upcast;
use super::*;
@@ -786,7 +803,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -810,7 +827,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
warning[test-diagnostic]: main diagnostic message
warning: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -830,7 +847,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
info[test-diagnostic]: main diagnostic message
info: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -857,7 +874,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -876,7 +893,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -897,7 +914,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> non-ascii:5:1
|
3 | ΔΔΔΔΔΔΔΔΔΔΔΔ
@@ -916,7 +933,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> non-ascii:2:2
|
1 | ☃☃☃☃☃☃☃☃☃☃☃☃
@@ -940,7 +957,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
4 | dog
@@ -957,7 +974,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
5 | elephant
@@ -972,7 +989,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -989,7 +1006,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:11:1
|
9 | inchworm
@@ -1006,7 +1023,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
1 | aardvark
@@ -1039,7 +1056,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1083,7 +1100,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1108,7 +1125,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1136,7 +1153,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1164,7 +1181,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1189,7 +1206,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1220,7 +1237,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:1:1
|
1 | aardvark
@@ -1258,7 +1275,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> spacey-animals:8:1
|
7 | dog
@@ -1275,7 +1292,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> spacey-animals:12:1
|
11 | gorilla
@@ -1293,7 +1310,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> spacey-animals:13:1
|
11 | gorilla
@@ -1333,7 +1350,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> spacey-animals:3:1
|
3 | beetle
@@ -1362,7 +1379,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1399,7 +1416,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1436,7 +1453,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1464,7 +1481,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1500,7 +1517,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1539,7 +1556,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1587,7 +1604,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:3:1
|
1 | aardvark
@@ -1623,7 +1640,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1646,7 +1663,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1666,7 +1683,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1686,7 +1703,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:4
|
3 | canary
@@ -1708,7 +1725,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:4
|
3 | canary
@@ -1740,7 +1757,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:4:1
|
2 | beetle
@@ -1769,7 +1786,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:4:1
|
2 | beetle
@@ -1800,7 +1817,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1835,7 +1852,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1863,7 +1880,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
3 | canary
@@ -1895,7 +1912,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:3
|
3 | canary
@@ -1917,7 +1934,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:3
|
3 | canary
@@ -1950,7 +1967,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:8:1
|
6 | finch
@@ -1990,7 +2007,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:5:1
|
5 | elephant
@@ -2034,7 +2051,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> fruits:1:1
|
1 | apple
@@ -2069,7 +2086,7 @@ watermelon
insta::assert_snapshot!(
env.render(&diag),
@r"
error[test-diagnostic]: main diagnostic message
error: lint:test-diagnostic: main diagnostic message
--> animals:11:1
|
11 | kangaroo
@@ -2157,9 +2174,8 @@ watermelon
fn span(&self, path: &str, line_offset_start: &str, line_offset_end: &str) -> Span {
let span = self.path(path);
let file = span.expect_ty_file();
let text = source_text(&self.db, file);
let line_index = line_index(&self.db, file);
let text = source_text(&self.db, span.file());
let line_index = line_index(&self.db, span.file());
let source = SourceCode::new(text.as_str(), &line_index);
let (line_start, offset_start) = parse_line_offset(line_offset_start);
@@ -2221,7 +2237,7 @@ watermelon
///
/// (This will set the "printed" flag on `Diagnostic`.)
fn render(&self, diag: &Diagnostic) -> String {
diag.display(&self.db.upcast(), &self.config).to_string()
diag.display(&self.db, &self.config).to_string()
}
}

View File

@@ -277,7 +277,7 @@ impl std::panic::RefUnwindSafe for Files {}
#[salsa::input]
pub struct File {
/// The path of the file (immutable).
#[returns(ref)]
#[return_ref]
pub path: FilePath,
/// The unix permissions of the file. Only supported on unix systems. Always `None` on Windows

View File

@@ -19,8 +19,8 @@ use crate::Db;
#[salsa::input(debug)]
pub struct FileRoot {
/// The path of a root is guaranteed to never change.
#[returns(deref)]
pub path: SystemPathBuf,
#[return_ref]
path_buf: SystemPathBuf,
/// The kind of the root at the time of its creation.
kind_at_time_of_creation: FileRootKind,
@@ -32,6 +32,10 @@ pub struct FileRoot {
}
impl FileRoot {
pub fn path(self, db: &dyn Db) -> &SystemPath {
self.path_buf(db)
}
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
self.kind_at_time_of_creation(db).durability()
}

View File

@@ -67,7 +67,7 @@ mod tests {
use crate::system::TestSystem;
use crate::system::{DbWithTestSystem, System};
use crate::vendored::VendoredFileSystem;
use crate::{Db, Upcast};
use crate::Db;
type Events = Arc<Mutex<Vec<salsa::Event>>>;
@@ -136,16 +136,7 @@ mod tests {
}
fn python_version(&self) -> ruff_python_ast::PythonVersion {
ruff_python_ast::PythonVersion::latest_ty()
}
}
impl Upcast<dyn Db> for TestDb {
fn upcast(&self) -> &(dyn Db + 'static) {
self
}
fn upcast_mut(&mut self) -> &mut (dyn Db + 'static) {
self
ruff_python_ast::PythonVersion::latest()
}
}

View File

@@ -20,7 +20,7 @@ use crate::Db;
/// reflected in the changed AST offsets.
/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires
/// for determining if a query result is unchanged.
#[salsa::tracked(returns(ref), no_eq)]
#[salsa::tracked(return_ref, no_eq)]
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
let _span = tracing::trace_span!("parsed_module", ?file).entered();

View File

@@ -11,14 +11,12 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ruff = { workspace = true }
ruff_diagnostics = { workspace = true }
ruff_formatter = { workspace = true }
ruff_linter = { workspace = true, features = ["schemars"] }
ruff_notebook = { workspace = true }
ruff_options_metadata = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_codegen = { workspace = true }
ruff_python_formatter = { workspace = true }
@@ -33,7 +31,6 @@ imara-diff = { workspace = true }
indicatif = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
markdown = { version = "1.0.0" }
pretty_assertions = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
@@ -47,7 +44,6 @@ toml = { workspace = true, features = ["parse"] }
tracing = { workspace = true }
tracing-indicatif = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
url = { workspace = true }
[dev-dependencies]
indoc = { workspace = true }

View File

@@ -2,10 +2,7 @@
use anyhow::Result;
use crate::{
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
generate_ty_options, generate_ty_rules, generate_ty_schema,
};
use crate::{generate_cli_help, generate_docs, generate_json_schema, generate_ty_schema};
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
@@ -41,8 +38,5 @@ pub(crate) fn main(args: &Args) -> Result<()> {
generate_docs::main(&generate_docs::Args {
dry_run: args.mode.is_dry_run(),
})?;
generate_ty_options::main(&generate_ty_options::Args { mode: args.mode })?;
generate_ty_rules::main(&generate_ty_rules::Args { mode: args.mode })?;
generate_ty_cli_reference::main(&generate_ty_cli_reference::Args { mode: args.mode })?;
Ok(())
}

View File

@@ -1,4 +1,5 @@
//! Generate CLI help.
#![allow(clippy::print_stdout)]
use std::path::PathBuf;
use std::{fs, str};

View File

@@ -1,4 +1,5 @@
//! Generate Markdown documentation for applicable rules.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::collections::HashSet;
use std::fmt::Write as _;
@@ -12,8 +13,8 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::FixAvailability;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use ruff_options_metadata::{OptionEntry, OptionsMetadata};
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
use crate::ROOT_DIR;

View File

@@ -1,3 +1,5 @@
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -4,9 +4,9 @@
use itertools::Itertools;
use std::fmt::Write;
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
use ruff_python_trivia::textwrap;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
pub(crate) fn generate() -> String {
let mut output = String::new();

View File

@@ -11,8 +11,8 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::FixAvailability;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
use ruff_options_metadata::OptionsMetadata;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::OptionsMetadata;
const FIX_SYMBOL: &str = "🛠️";
const PREVIEW_SYMBOL: &str = "🧪";

View File

@@ -1,334 +0,0 @@
//! Generate a Markdown-compatible reference for the ty command-line interface.
use std::cmp::max;
use std::path::PathBuf;
use anyhow::{bail, Result};
use clap::{Command, CommandFactory};
use itertools::Itertools;
use pretty_assertions::StrComparison;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
use crate::ROOT_DIR;
use ty::Cli;
const SHOW_HIDDEN_COMMANDS: &[&str] = &["generate-shell-completion"];
#[derive(clap::Args)]
pub(crate) struct Args {
#[arg(long, default_value_t, value_enum)]
pub(crate) mode: Mode,
}
pub(crate) fn main(args: &Args) -> Result<()> {
let reference_string = generate();
let filename = "crates/ty/docs/cli.md";
let reference_path = PathBuf::from(ROOT_DIR).join(filename);
match args.mode {
Mode::DryRun => {
println!("{reference_string}");
}
Mode::Check => {
match std::fs::read_to_string(reference_path) {
Ok(current) => {
if current == reference_string {
println!("Up-to-date: {filename}");
} else {
let comparison = StrComparison::new(&current, &reference_string);
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
bail!("{filename} not found, please run `{REGENERATE_ALL_COMMAND}`");
}
Err(err) => {
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{err}");
}
}
}
Mode::Write => match std::fs::read_to_string(&reference_path) {
Ok(current) => {
if current == reference_string {
println!("Up-to-date: {filename}");
} else {
println!("Updating: {filename}");
std::fs::write(reference_path, reference_string.as_bytes())?;
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
println!("Updating: {filename}");
std::fs::write(reference_path, reference_string.as_bytes())?;
}
Err(err) => {
bail!("{filename} changed, please run `cargo dev generate-cli-reference`:\n{err}");
}
},
}
Ok(())
}
fn generate() -> String {
let mut output = String::new();
let mut ty = Cli::command();
// It is very important to build the command before beginning inspection or subcommands
// will be missing all of the propagated options.
ty.build();
let mut parents = Vec::new();
output.push_str("# CLI Reference\n\n");
generate_command(&mut output, &ty, &mut parents);
output
}
#[allow(clippy::format_push_string)]
fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut Vec<&'a Command>) {
if command.is_hide_set() && !SHOW_HIDDEN_COMMANDS.contains(&command.get_name()) {
return;
}
// Generate the command header.
let name = if parents.is_empty() {
command.get_name().to_string()
} else {
format!(
"{} {}",
parents.iter().map(|cmd| cmd.get_name()).join(" "),
command.get_name()
)
};
// Display the top-level `ty` command at the same level as its children
let level = max(2, parents.len() + 1);
output.push_str(&format!("{} {name}\n\n", "#".repeat(level)));
// Display the command description.
if let Some(about) = command.get_long_about().or_else(|| command.get_about()) {
output.push_str(&about.to_string());
output.push_str("\n\n");
}
// Display the usage
{
// This appears to be the simplest way to get rendered usage from Clap,
// it is complicated to render it manually. It's annoying that it
// requires a mutable reference but it doesn't really matter.
let mut command = command.clone();
output.push_str("<h3 class=\"cli-reference\">Usage</h3>\n\n");
output.push_str(&format!(
"```\n{}\n```",
command
.render_usage()
.to_string()
.trim_start_matches("Usage: "),
));
output.push_str("\n\n");
}
if command.get_name() == "help" {
return;
}
// Display a list of child commands
let mut subcommands = command.get_subcommands().peekable();
let has_subcommands = subcommands.peek().is_some();
if has_subcommands {
output.push_str("<h3 class=\"cli-reference\">Commands</h3>\n\n");
output.push_str("<dl class=\"cli-reference\">");
for subcommand in subcommands {
if subcommand.is_hide_set() {
continue;
}
let subcommand_name = format!("{name} {}", subcommand.get_name());
output.push_str(&format!(
"<dt><a href=\"#{}\"><code>{subcommand_name}</code></a></dt>",
subcommand_name.replace(' ', "-")
));
if let Some(about) = subcommand.get_about() {
output.push_str(&format!(
"<dd>{}</dd>\n",
markdown::to_html(&about.to_string())
));
}
}
output.push_str("</dl>\n\n");
}
// Do not display options for commands with children
if !has_subcommands {
let name_key = name.replace(' ', "-");
// Display positional arguments
let mut arguments = command
.get_positionals()
.filter(|arg| !arg.is_hide_set())
.peekable();
if arguments.peek().is_some() {
output.push_str("<h3 class=\"cli-reference\">Arguments</h3>\n\n");
output.push_str("<dl class=\"cli-reference\">");
for arg in arguments {
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>",
arg.get_id().to_string().to_uppercase(),
));
output.push_str("</dt>");
if let Some(help) = arg.get_long_help().or_else(|| arg.get_help()) {
output.push_str("<dd>");
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
output.push_str("</dd>");
}
}
output.push_str("</dl>\n\n");
}
// Display options and flags
let mut options = command
.get_arguments()
.filter(|arg| !arg.is_positional())
.filter(|arg| !arg.is_hide_set())
.sorted_by_key(|arg| arg.get_id())
.peekable();
if options.peek().is_some() {
output.push_str("<h3 class=\"cli-reference\">Options</h3>\n\n");
output.push_str("<dl class=\"cli-reference\">");
for opt in options {
let Some(long) = opt.get_long() else { continue };
let id = format!("{name_key}--{long}");
output.push_str(&format!("<dt id=\"{id}\">"));
output.push_str(&format!("<a href=\"#{id}\"><code>--{long}</code></a>"));
for long_alias in opt.get_all_aliases().into_iter().flatten() {
output.push_str(&format!(", <code>--{long_alias}</code>"));
}
if let Some(short) = opt.get_short() {
output.push_str(&format!(", <code>-{short}</code>"));
}
for short_alias in opt.get_all_short_aliases().into_iter().flatten() {
output.push_str(&format!(", <code>-{short_alias}</code>"));
}
// Re-implements private `Arg::is_takes_value_set` used in `Command::get_opts`
if opt
.get_num_args()
.unwrap_or_else(|| 1.into())
.takes_values()
{
if let Some(values) = opt.get_value_names() {
for value in values {
output.push_str(&format!(
" <i>{}</i>",
value.to_lowercase().replace('_', "-")
));
}
}
}
output.push_str("</dt>");
if let Some(help) = opt.get_long_help().or_else(|| opt.get_help()) {
output.push_str("<dd>");
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
emit_env_option(opt, output);
emit_default_option(opt, output);
emit_possible_options(opt, output);
output.push_str("</dd>");
}
}
output.push_str("</dl>");
}
output.push_str("\n\n");
}
parents.push(command);
// Recurse to all of the subcommands.
for subcommand in command.get_subcommands() {
generate_command(output, subcommand, parents);
}
parents.pop();
}
fn emit_env_option(opt: &clap::Arg, output: &mut String) {
if opt.is_hide_env_set() {
return;
}
if let Some(env) = opt.get_env() {
output.push_str(&markdown::to_html(&format!(
"May also be set with the `{}` environment variable.",
env.to_string_lossy()
)));
}
}
fn emit_default_option(opt: &clap::Arg, output: &mut String) {
if opt.is_hide_default_value_set() || !opt.get_num_args().expect("built").takes_values() {
return;
}
let values = opt.get_default_values();
if !values.is_empty() {
let value = format!(
"\n[default: {}]",
opt.get_default_values()
.iter()
.map(|s| s.to_string_lossy())
.join(",")
);
output.push_str(&markdown::to_html(&value));
}
}
fn emit_possible_options(opt: &clap::Arg, output: &mut String) {
if opt.is_hide_possible_values_set() {
return;
}
let values = opt.get_possible_values();
if !values.is_empty() {
let value = format!(
"\nPossible values:\n{}",
values
.into_iter()
.filter(|value| !value.is_hide_set())
.map(|value| {
let name = value.get_name();
value.get_help().map_or_else(
|| format!(" - `{name}`"),
|help| format!(" - `{name}`: {help}"),
)
})
.collect_vec()
.join("\n"),
);
output.push_str(&markdown::to_html(&value));
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::generate_all::Mode;
use super::{main, Args};
#[test]
fn ty_cli_reference_is_up_to_date() -> Result<()> {
main(&Args { mode: Mode::Check })
}
}

View File

@@ -1,257 +0,0 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
use anyhow::bail;
use itertools::Itertools;
use pretty_assertions::StrComparison;
use std::{fmt::Write, path::PathBuf};
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
use ty_project::metadata::Options;
use crate::{
generate_all::{Mode, REGENERATE_ALL_COMMAND},
ROOT_DIR,
};
#[derive(clap::Args)]
pub(crate) struct Args {
/// Write the generated table to stdout (rather than to `crates/ty/docs/configuration.md`).
#[arg(long, default_value_t, value_enum)]
pub(crate) mode: Mode,
}
pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
let mut output = String::new();
let file_name = "crates/ty/docs/configuration.md";
let markdown_path = PathBuf::from(ROOT_DIR).join(file_name);
generate_set(
&mut output,
Set::Toplevel(Options::metadata()),
&mut Vec::new(),
);
match args.mode {
Mode::DryRun => {
println!("{output}");
}
Mode::Check => {
let current = std::fs::read_to_string(&markdown_path)?;
if output == current {
println!("Up-to-date: {file_name}",);
} else {
let comparison = StrComparison::new(&current, &output);
bail!("{file_name} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}",);
}
}
Mode::Write => {
let current = std::fs::read_to_string(&markdown_path)?;
if current == output {
println!("Up-to-date: {file_name}",);
} else {
println!("Updating: {file_name}",);
std::fs::write(markdown_path, output.as_bytes())?;
}
}
}
Ok(())
}
fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
match &set {
Set::Toplevel(_) => {
output.push_str("# Configuration\n");
}
Set::Named { name, .. } => {
let title = parents
.iter()
.filter_map(|set| set.name())
.chain(std::iter::once(name.as_str()))
.join(".");
writeln!(output, "## `{title}`\n",).unwrap();
}
}
if let Some(documentation) = set.metadata().documentation() {
output.push_str(documentation);
output.push('\n');
output.push('\n');
}
let mut visitor = CollectOptionsVisitor::default();
set.metadata().record(&mut visitor);
let (mut fields, mut sets) = (visitor.fields, visitor.groups);
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
parents.push(set);
// Generate the fields.
for (name, field) in &fields {
emit_field(output, name, field, parents.as_slice());
output.push_str("---\n\n");
}
// Generate all the sub-sets.
for (set_name, sub_set) in &sets {
generate_set(
output,
Set::Named {
name: set_name.to_string(),
set: *sub_set,
},
parents,
);
}
parents.pop();
}
enum Set {
Toplevel(OptionSet),
Named { name: String, set: OptionSet },
}
impl Set {
fn name(&self) -> Option<&str> {
match self {
Set::Toplevel(_) => None,
Set::Named { name, .. } => Some(name),
}
}
fn metadata(&self) -> &OptionSet {
match self {
Set::Toplevel(set) => set,
Set::Named { set, .. } => 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}`");
output.push('\n');
if let Some(deprecated) = &field.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();
}
output.push('.');
if let Some(message) = deprecated.message {
writeln!(output, " {message}").unwrap();
}
output.push('\n');
}
output.push_str(field.doc);
output.push_str("\n\n");
let _ = writeln!(output, "**Default value**: `{}`", field.default);
output.push('\n');
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
output.push('\n');
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
output.push_str(&format_example(
&format_header(
field.scope,
field.example,
parents,
ConfigurationFile::PyprojectToml,
),
field.example,
));
output.push('\n');
}
fn format_example(header: &str, content: &str) -> String {
if header.is_empty() {
format!("```toml\n{content}\n```\n",)
} else {
format!("```toml\n{header}\n{content}\n```\n",)
}
}
/// Format the TOML header for the example usage for a given option.
///
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
fn format_header(
scope: Option<&str>,
example: &str,
parents: &[Set],
configuration: ConfigurationFile,
) -> String {
let tool_parent = match configuration {
ConfigurationFile::PyprojectToml => Some("tool.ty"),
ConfigurationFile::TyToml => None,
};
let header = tool_parent
.into_iter()
.chain(parents.iter().filter_map(|parent| parent.name()))
.chain(scope)
.join(".");
// Ex) `[[tool.ty.xx]]`
if example.starts_with(&format!("[[{header}")) {
return String::new();
}
// Ex) `[tool.ty.rules]`
if example.starts_with(&format!("[{header}")) {
return String::new();
}
if header.is_empty() {
String::new()
} else {
format!("[{header}]")
}
}
#[derive(Default)]
struct CollectOptionsVisitor {
groups: Vec<(String, OptionSet)>,
fields: Vec<(String, OptionField)>,
}
impl Visit for CollectOptionsVisitor {
fn record_set(&mut self, name: &str, group: OptionSet) {
self.groups.push((name.to_owned(), group));
}
fn record_field(&mut self, name: &str, field: OptionField) {
self.fields.push((name.to_owned(), field));
}
}
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
#[expect(dead_code)]
TyToml,
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::generate_all::Mode;
use super::{main, Args};
#[test]
fn ty_configuration_markdown_up_to_date() -> Result<()> {
main(&Args { mode: Mode::Check })?;
Ok(())
}
}

View File

@@ -1,143 +0,0 @@
//! Generates the rules table for ty
use std::borrow::Cow;
use std::fmt::Write as _;
use std::fs;
use std::path::PathBuf;
use anyhow::{bail, Result};
use itertools::Itertools as _;
use pretty_assertions::StrComparison;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
use crate::ROOT_DIR;
#[derive(clap::Args)]
pub(crate) struct Args {
/// Write the generated table to stdout (rather than to `ty.schema.json`).
#[arg(long, default_value_t, value_enum)]
pub(crate) mode: Mode,
}
pub(crate) fn main(args: &Args) -> Result<()> {
let markdown = generate_markdown();
let filename = "crates/ty/docs/rules.md";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
match args.mode {
Mode::DryRun => {
println!("{markdown}");
}
Mode::Check => {
let current = fs::read_to_string(schema_path)?;
if current == markdown {
println!("Up-to-date: {filename}");
} else {
let comparison = StrComparison::new(&current, &markdown);
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
}
}
Mode::Write => {
let current = fs::read_to_string(&schema_path)?;
if current == markdown {
println!("Up-to-date: {filename}");
} else {
println!("Updating: {filename}");
fs::write(schema_path, markdown.as_bytes())?;
}
}
}
Ok(())
}
fn generate_markdown() -> String {
let registry = &*ty_project::DEFAULT_LINT_REGISTRY;
let mut output = String::new();
let _ = writeln!(&mut output, "# Rules\n");
let mut lints: Vec<_> = registry.lints().iter().collect();
lints.sort_by(|a, b| {
a.default_level()
.cmp(&b.default_level())
.reverse()
.then_with(|| a.name().cmp(&b.name()))
});
for lint in lints {
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
// Increase the header-level by one
let documentation = lint
.documentation_lines()
.map(|line| {
if line.starts_with('#') {
Cow::Owned(format!("#{line}"))
} else {
Cow::Borrowed(line)
}
})
.join("\n");
let _ = writeln!(
&mut output,
r#"**Default level**: {level}
<details>
<summary>{summary}</summary>
{documentation}
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name})
* [View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
</details>
"#,
level = lint.default_level(),
// GitHub doesn't support markdown in `summary` headers
summary = replace_inline_code(lint.summary()),
encoded_name = url::form_urlencoded::byte_serialize(lint.name().as_str().as_bytes())
.collect::<String>(),
file = url::form_urlencoded::byte_serialize(lint.file().replace('\\', "/").as_bytes())
.collect::<String>(),
line = lint.line(),
);
}
output
}
/// Replaces inline code blocks (`code`) with `<code>code</code>`
fn replace_inline_code(input: &str) -> String {
let mut output = String::new();
let mut parts = input.split('`');
while let Some(before) = parts.next() {
if let Some(between) = parts.next() {
output.push_str(before);
output.push_str("<code>");
output.push_str(between);
output.push_str("</code>");
} else {
output.push_str(before);
}
}
output
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::generate_all::Mode;
use super::{main, Args};
#[test]
fn ty_rules_up_to_date() -> Result<()> {
main(&Args { mode: Mode::Check })
}
}

View File

@@ -1,3 +1,5 @@
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -2,8 +2,6 @@
//!
//! Within the ruff repository you can run it with `cargo dev`.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff::{args::GlobalConfigArgs, check};
@@ -17,9 +15,6 @@ mod generate_docs;
mod generate_json_schema;
mod generate_options;
mod generate_rules_table;
mod generate_ty_cli_reference;
mod generate_ty_options;
mod generate_ty_rules;
mod generate_ty_schema;
mod print_ast;
mod print_cst;
@@ -49,10 +44,8 @@ enum Command {
GenerateTySchema(generate_ty_schema::Args),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable,
GenerateTyRules(generate_ty_rules::Args),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions,
GenerateTyOptions(generate_ty_options::Args),
/// Generate CLI help.
GenerateCliHelp(generate_cli_help::Args),
/// Generate Markdown docs.
@@ -95,9 +88,7 @@ fn main() -> Result<ExitCode> {
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
Command::GenerateTySchema(args) => generate_ty_schema::main(&args)?,
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
Command::GenerateTyRules(args) => generate_ty_rules::main(&args)?,
Command::GenerateOptions => println!("{}", generate_options::generate()),
Command::GenerateTyOptions(args) => generate_ty_options::main(&args)?,
Command::GenerateCliHelp(args) => generate_cli_help::main(&args)?,
Command::GenerateDocs(args) => generate_docs::main(&args)?,
Command::PrintAST(args) => print_ast::main(&args)?,

View File

@@ -1,4 +1,5 @@
//! Print the AST for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Print the `LibCST` CST for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Print the token stream for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Run round-trip source code generation on a given Python or Jupyter notebook file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -92,7 +92,7 @@ impl std::fmt::Display for IndentStyle {
}
}
/// The visual width of an indentation.
/// The visual width of a 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 these format options
/// Derives the print options from the these format options
fn as_print_options(&self) -> PrinterOptions;
}

View File

@@ -88,8 +88,8 @@ impl Db for ModuleDb {
!file.path(self).is_vendored_path()
}
fn rule_selection(&self) -> &RuleSelection {
&self.rule_selection
fn rule_selection(&self) -> Arc<RuleSelection> {
self.rule_selection.clone()
}
fn lint_registry(&self) -> &LintRegistry {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.11.9"
version = "0.11.8"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -15,7 +15,6 @@ license = { workspace = true }
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_cache = { workspace = true }
ruff_db = { workspace = true }
ruff_diagnostics = { workspace = true, features = ["serde"] }
ruff_notebook = { workspace = true }
ruff_macros = { workspace = true }

View File

@@ -1,8 +1,17 @@
from __future__ import annotations
try:
from airflow.assets.manager import AssetManager
from airflow.sdk import Asset
except ModuleNotFoundError:
from airflow.datasets.manager import DatasetManager as AssetManager
from airflow.datasets import Dataset as Asset
AssetManager()
Asset
try:
from airflow.sdk import Asset
except ModuleNotFoundError:
from airflow import datasets
Asset = datasets.Dataset
asset = Asset()

View File

@@ -91,14 +91,14 @@ from airflow.operators.sql import (
BaseSQLOperator,
BranchSQLOperator,
SQLColumnCheckOperator,
SQLTableCheckOperator,
SQLTablecheckOperator,
_convert_to_float_if_possible,
parse_boolean,
)
BaseSQLOperator()
BranchSQLOperator()
SQLTableCheckOperator()
SQLTablecheckOperator()
SQLColumnCheckOperator()
_convert_to_float_if_possible()
parse_boolean()

View File

@@ -1,8 +0,0 @@
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()

View File

@@ -13,10 +13,6 @@ 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
@@ -46,9 +42,7 @@ ObjectStoragePath()
attach()
# airflow.models
Connection()
DAGFromModel()
Variable()
# airflow.models.baseoperator
chain()

View File

@@ -1,8 +0,0 @@
from __future__ import annotations
try:
from airflow.sdk import Asset
except ModuleNotFoundError:
from airflow.datasets import Dataset as Asset
Asset()

View File

@@ -1,8 +0,0 @@
from __future__ import annotations
try:
from airflow.providers.standard.triggers.file import FileTrigger
except ModuleNotFoundError:
from airflow.triggers.file import FileTrigger
FileTrigger()

View File

@@ -166,6 +166,3 @@ query60 = f"""
foo
FROM ({user_input}) raw
"""
# https://github.com/astral-sh/ruff/issues/17967
query61 = f"SELECT * FROM table" # skip expressionless f-strings

View File

@@ -25,11 +25,3 @@ 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)

View File

@@ -28,14 +28,3 @@ 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,
}
)

View File

@@ -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,7 +106,3 @@ 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))

View File

@@ -9,7 +9,3 @@ 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))

View File

@@ -87,20 +87,3 @@ 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)

View File

@@ -144,14 +144,14 @@ def f():
def f():
# make sure that `tmp` is not deleted
tmp = 1; result = [] # comment should be protected
tmp = 1; result = [] # commment 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 # comment should be protected
result = []; tmp = 1 # commment should be protected
for i in range(10):
result.append(i + 1) # PERF401

View File

@@ -1,6 +1,3 @@
import datetime
import sys
num = 1337
def return_num() -> int:
@@ -13,7 +10,6 @@ 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)
@@ -26,19 +22,3 @@ 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:])

View File

@@ -19,9 +19,6 @@ 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]
@@ -38,7 +35,3 @@ 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()}

View File

@@ -1116,95 +1116,6 @@ 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,

View File

@@ -17,7 +17,7 @@ impl Emitter for AzureEmitter {
context: &EmitterContext,
) -> anyhow::Result<()> {
for message in messages {
let location = if context.is_notebook(&message.filename()) {
let location = if context.is_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()

View File

@@ -22,7 +22,7 @@ use crate::text_helpers::ShowNonprinting;
/// * Compute the diff from the [`Edit`] because diff calculation is expensive.
pub(super) struct Diff<'a> {
fix: &'a Fix,
source_code: SourceFile,
source_code: &'a SourceFile,
}
impl<'a> Diff<'a> {

View File

@@ -19,7 +19,7 @@ impl Emitter for GithubEmitter {
) -> anyhow::Result<()> {
for message in messages {
let source_location = message.compute_start_location();
let location = if context.is_notebook(&message.filename()) {
let location = if context.is_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()
@@ -43,7 +43,7 @@ impl Emitter for GithubEmitter {
write!(
writer,
"{path}:{row}:{column}:",
path = relativize_path(&*message.filename()),
path = relativize_path(message.filename()),
row = location.line,
column = location.column,
)?;

View File

@@ -62,7 +62,7 @@ impl Serialize for SerializedMessages<'_> {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let lines = if self.context.is_notebook(&message.filename()) {
let lines = if self.context.is_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
json!({
@@ -77,8 +77,8 @@ impl Serialize for SerializedMessages<'_> {
};
let path = self.project_dir.as_ref().map_or_else(
|| relativize_path(&*message.filename()),
|project_dir| relativize_path_to(&*message.filename(), project_dir),
|| relativize_path(message.filename()),
|project_dir| relativize_path_to(message.filename(), project_dir),
);
let mut message_fingerprint = fingerprint(message, &path, 0);

View File

@@ -65,7 +65,7 @@ impl Emitter for GroupedEmitter {
let column_length = calculate_print_width(max_column_length);
// Print the filename.
writeln!(writer, "{}:", relativize_path(&*filename).underline())?;
writeln!(writer, "{}:", relativize_path(filename).underline())?;
// Print each message.
for message in messages {
@@ -73,7 +73,7 @@ impl Emitter for GroupedEmitter {
writer,
"{}",
DisplayGroupedMessage {
notebook_index: context.notebook_index(&message.filename()),
notebook_index: context.notebook_index(message.filename()),
message,
show_fix_status: self.show_fix_status,
unsafe_fixes: self.unsafe_fixes,

View File

@@ -49,9 +49,8 @@ impl Serialize for ExpandedMessages<'_> {
}
pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) -> Value {
let source_file = message.source_file();
let source_code = source_file.to_source_code();
let notebook_index = context.notebook_index(&message.filename());
let source_code = message.source_file().to_source_code();
let notebook_index = context.notebook_index(message.filename());
let fix = message.fix().map(|fix| {
json!({

View File

@@ -32,7 +32,7 @@ impl Emitter for JunitEmitter {
report.add_test_suite(test_suite);
} else {
for (filename, messages) in group_messages_by_filename(messages) {
let mut test_suite = TestSuite::new(&filename);
let mut test_suite = TestSuite::new(filename);
test_suite
.extra
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
@@ -44,7 +44,7 @@ impl Emitter for JunitEmitter {
} = message;
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
status.set_message(message.body());
let location = if context.is_notebook(&message.filename()) {
let location = if context.is_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()
@@ -66,7 +66,7 @@ impl Emitter for JunitEmitter {
},
status,
);
let file_path = Path::new(&*filename);
let file_path = Path::new(filename);
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let classname = file_path.parent().unwrap().join(file_stem);
case.set_classname(classname.to_str().unwrap());

View File

@@ -1,10 +1,8 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::io::Write;
use std::ops::Deref;
use ruff_db::diagnostic::{self as db, Annotation, DiagnosticId, Severity, Span};
use ruff_python_parser::semantic_errors::SemanticSyntaxError;
use rustc_hash::FxHashMap;
@@ -47,7 +45,7 @@ mod text;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Message {
Diagnostic(DiagnosticMessage),
SyntaxError(db::Diagnostic),
SyntaxError(SyntaxErrorMessage),
}
/// A diagnostic message corresponding to a rule violation.
@@ -61,6 +59,14 @@ pub struct DiagnosticMessage {
pub noqa_offset: TextSize,
}
/// A syntax error message raised by the parser.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyntaxErrorMessage {
pub message: String,
pub range: TextRange,
pub file: SourceFile,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum MessageKind {
Diagnostic(Rule),
@@ -77,17 +83,6 @@ impl MessageKind {
}
impl Message {
pub fn syntax_error(
message: impl std::fmt::Display,
range: TextRange,
file: SourceFile,
) -> Message {
let mut diag = db::Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
let span = Span::from(file).with_range(range);
diag.annotate(Annotation::primary(span).message(message));
Self::SyntaxError(diag)
}
/// Create a [`Message`] from the given [`Diagnostic`] corresponding to a rule violation.
pub fn from_diagnostic(
diagnostic: Diagnostic,
@@ -119,14 +114,14 @@ impl Message {
.next()
.map_or(TextSize::new(0), TextLen::text_len);
Message::syntax_error(
format_args!(
Message::SyntaxError(SyntaxErrorMessage {
message: format!(
"SyntaxError: {}",
DisplayParseErrorType::new(&parse_error.error)
),
TextRange::at(parse_error.location.start(), len),
range: TextRange::at(parse_error.location.start(), len),
file,
)
})
}
/// Create a [`Message`] from the given [`UnsupportedSyntaxError`].
@@ -134,11 +129,11 @@ impl Message {
unsupported_syntax_error: &UnsupportedSyntaxError,
file: SourceFile,
) -> Message {
Message::syntax_error(
format_args!("SyntaxError: {unsupported_syntax_error}"),
unsupported_syntax_error.range,
Message::SyntaxError(SyntaxErrorMessage {
message: format!("SyntaxError: {unsupported_syntax_error}"),
range: unsupported_syntax_error.range,
file,
)
})
}
/// Create a [`Message`] from the given [`SemanticSyntaxError`].
@@ -146,11 +141,11 @@ impl Message {
semantic_syntax_error: &SemanticSyntaxError,
file: SourceFile,
) -> Message {
Message::syntax_error(
format_args!("SyntaxError: {semantic_syntax_error}"),
semantic_syntax_error.range,
Message::SyntaxError(SyntaxErrorMessage {
message: format!("SyntaxError: {semantic_syntax_error}"),
range: semantic_syntax_error.range,
file,
)
})
}
pub const fn as_diagnostic_message(&self) -> Option<&DiagnosticMessage> {
@@ -173,11 +168,8 @@ impl Message {
}
/// Returns `true` if `self` is a syntax error message.
pub fn is_syntax_error(&self) -> bool {
match self {
Message::Diagnostic(_) => false,
Message::SyntaxError(diag) => diag.id().is_invalid_syntax(),
}
pub const fn is_syntax_error(&self) -> bool {
matches!(self, Message::SyntaxError(_))
}
/// Returns a message kind.
@@ -200,11 +192,7 @@ impl Message {
pub fn body(&self) -> &str {
match self {
Message::Diagnostic(m) => &m.kind.body,
Message::SyntaxError(m) => m
.primary_annotation()
.expect("Expected a primary annotation for a ruff diagnostic")
.get_message()
.expect("Expected a message for a ruff diagnostic"),
Message::SyntaxError(m) => &m.message,
}
}
@@ -246,47 +234,27 @@ impl Message {
}
/// Returns the filename for the message.
pub fn filename(&self) -> Cow<'_, str> {
match self {
Message::Diagnostic(m) => Cow::Borrowed(m.file.name()),
Message::SyntaxError(diag) => Cow::Owned(
diag.expect_primary_span()
.expect_ruff_file()
.name()
.to_string(),
),
}
pub fn filename(&self) -> &str {
self.source_file().name()
}
/// Computes the start source location for the message.
pub fn compute_start_location(&self) -> LineColumn {
match self {
Message::Diagnostic(m) => m.file.to_source_code().line_column(m.range.start()),
Message::SyntaxError(diag) => diag
.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.start()),
}
self.source_file()
.to_source_code()
.line_column(self.start())
}
/// Computes the end source location for the message.
pub fn compute_end_location(&self) -> LineColumn {
match self {
Message::Diagnostic(m) => m.file.to_source_code().line_column(m.range.end()),
Message::SyntaxError(diag) => diag
.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.end()),
}
self.source_file().to_source_code().line_column(self.end())
}
/// Returns the [`SourceFile`] which the message belongs to.
pub fn source_file(&self) -> SourceFile {
pub fn source_file(&self) -> &SourceFile {
match self {
Message::Diagnostic(m) => m.file.clone(),
Message::SyntaxError(m) => m.expect_primary_span().expect_ruff_file().clone(),
Message::Diagnostic(m) => &m.file,
Message::SyntaxError(m) => &m.file,
}
}
}
@@ -307,10 +275,7 @@ impl Ranged for Message {
fn range(&self) -> TextRange {
match self {
Message::Diagnostic(m) => m.range,
Message::SyntaxError(m) => m
.expect_primary_span()
.range()
.expect("Expected range for ruff span"),
Message::SyntaxError(m) => m.range,
}
}
}
@@ -328,11 +293,11 @@ impl Deref for MessageWithLocation<'_> {
}
}
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<String, Vec<MessageWithLocation>> {
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&str, Vec<MessageWithLocation>> {
let mut grouped_messages = BTreeMap::default();
for message in messages {
grouped_messages
.entry(message.filename().to_string())
.entry(message.filename())
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message,

View File

@@ -18,7 +18,7 @@ impl Emitter for PylintEmitter {
context: &EmitterContext,
) -> anyhow::Result<()> {
for message in messages {
let row = if context.is_notebook(&message.filename()) {
let row = if context.is_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
OneIndexed::from_zero_indexed(0)
@@ -39,7 +39,7 @@ impl Emitter for PylintEmitter {
writeln!(
writer,
"{path}:{row}: {body}",
path = relativize_path(&*message.filename()),
path = relativize_path(message.filename()),
)?;
}

View File

@@ -57,8 +57,7 @@ impl Serialize for ExpandedMessages<'_> {
}
fn message_to_rdjson_value(message: &Message) -> Value {
let source_file = message.source_file();
let source_code = source_file.to_source_code();
let source_code = message.source_file().to_source_code();
let start_location = source_code.line_column(message.start());
let end_location = source_code.line_column(message.end());

View File

@@ -121,7 +121,7 @@ impl SarifResult {
fn from_message(message: &Message) -> Result<Self> {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let path = normalize_path(&*message.filename());
let path = normalize_path(message.filename());
Ok(Self {
rule: message.rule(),
level: "error".to_string(),
@@ -141,7 +141,7 @@ impl SarifResult {
fn from_message(message: &Message) -> Result<Self> {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let path = normalize_path(&*message.filename());
let path = normalize_path(message.filename());
Ok(Self {
rule: message.rule(),
level: "error".to_string(),

View File

@@ -73,12 +73,12 @@ impl Emitter for TextEmitter {
write!(
writer,
"{path}{sep}",
path = relativize_path(&*message.filename()).bold(),
path = relativize_path(message.filename()).bold(),
sep = ":".cyan(),
)?;
let start_location = message.compute_start_location();
let notebook_index = context.notebook_index(&message.filename());
let notebook_index = context.notebook_index(message.filename());
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
let diagnostic_location = if let Some(notebook_index) = notebook_index {
@@ -191,8 +191,7 @@ impl Display for MessageCodeFrame<'_> {
Vec::new()
};
let source_file = self.message.source_file();
let source_code = source_file.to_source_code();
let source_code = self.message.source_file().to_source_code();
let content_start_index = source_code.line_index(self.message.start());
let mut start_index = content_start_index.saturating_sub(2);

View File

@@ -45,8 +45,7 @@ pub(crate) enum ProviderReplacement {
pub(crate) fn is_guarded_by_try_except(
expr: &Expr,
module: &str,
name: &str,
replacement: &Replacement,
semantic: &SemanticModel,
) -> bool {
match expr {
@@ -64,7 +63,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, module, name, semantic)
try_block_contains_undeprecated_attribute(try_node, replacement, semantic)
}
Expr::Name(ExprName { id, .. }) => {
let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else {
@@ -90,7 +89,7 @@ pub(crate) fn is_guarded_by_try_except(
{
return false;
}
try_block_contains_undeprecated_import(try_node, module, name)
try_block_contains_undeprecated_import(try_node, replacement)
}
_ => false,
}
@@ -101,10 +100,12 @@ 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,
module: &str,
name: &str,
replacement: &Replacement,
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('.') {
@@ -121,7 +122,10 @@ 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, module: &str, name: &str) -> bool {
fn try_block_contains_undeprecated_import(try_node: &StmtTry, replacement: &Replacement) -> bool {
let Replacement::AutoImport { module, name } = replacement else {
return false;
};
let mut import_searcher = ImportSearcher::new(module, name);
import_searcher.visit_body(&try_node.body);
import_searcher.found_import

View File

@@ -46,12 +46,9 @@ 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(

View File

@@ -1,5 +1,5 @@
use crate::importer::ImportRequest;
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
use crate::rules::airflow::helpers::ProviderReplacement;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{Expr, ExprAttribute};
@@ -288,7 +288,7 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
}
}
["airflow", "operators", "sql", rest] => match *rest {
"BaseSQLOperator" | "BranchSQLOperator" | "SQLTableCheckOperator" => {
"BaseSQLOperator" | "BranchSQLOperator" | "SQLTablecheckOperator" => {
ProviderReplacement::SourceModuleMovedToProvider {
name: (*rest).to_string(),
module: "airflow.providers.common.sql.operators.sql",
@@ -937,17 +937,13 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
ranged.range(),
);
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;
}
if let ProviderReplacement::AutoImport {
module,
name,
provider: _,
version: _,
} = replacement
{
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(module, name),
@@ -958,5 +954,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
Ok(Fix::safe_edits(import_edit, [replacement_edit]))
});
}
checker.report_diagnostic(diagnostic);
}

View File

@@ -865,6 +865,10 @@ 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(
Airflow3Removal {
deprecated: qualified_name.to_string(),
@@ -872,15 +876,8 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
},
range,
);
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;
}
if let Replacement::AutoImport { module, name } = replacement {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(module, name),

View File

@@ -1,6 +1,6 @@
use crate::importer::ImportRequest;
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
use crate::rules::airflow::helpers::ProviderReplacement;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{Expr, ExprAttribute};
@@ -279,17 +279,13 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
ranged.range(),
);
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;
}
if let ProviderReplacement::AutoImport {
module,
name,
provider: _,
version: _,
} = replacement
{
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(module, name),

View File

@@ -242,14 +242,6 @@ 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 {
@@ -283,6 +275,10 @@ 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(),
@@ -291,15 +287,7 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
range,
);
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;
}
if let Replacement::AutoImport { module, name } = replacement {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(module, name),

View File

@@ -216,7 +216,7 @@ AIR302_common_sql.py:99:1: AIR302 `airflow.operators.sql.BaseSQLOperator` is mov
99 | BaseSQLOperator()
| ^^^^^^^^^^^^^^^ AIR302
100 | BranchSQLOperator()
101 | SQLTableCheckOperator()
101 | SQLTablecheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.BaseSQLOperator` instead.
@@ -225,26 +225,26 @@ AIR302_common_sql.py:100:1: AIR302 `airflow.operators.sql.BranchSQLOperator` is
99 | BaseSQLOperator()
100 | BranchSQLOperator()
| ^^^^^^^^^^^^^^^^^ AIR302
101 | SQLTableCheckOperator()
101 | SQLTablecheckOperator()
102 | SQLColumnCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.BranchSQLOperator` instead.
AIR302_common_sql.py:101:1: AIR302 `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302_common_sql.py:101:1: AIR302 `airflow.operators.sql.SQLTablecheckOperator` is moved into `common-sql` provider in Airflow 3.0;
|
99 | BaseSQLOperator()
100 | BranchSQLOperator()
101 | SQLTableCheckOperator()
101 | SQLTablecheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
102 | SQLColumnCheckOperator()
103 | _convert_to_float_if_possible()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLTableCheckOperator` instead.
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLTablecheckOperator` instead.
AIR302_common_sql.py:102:1: AIR302 `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
|
100 | BranchSQLOperator()
101 | SQLTableCheckOperator()
101 | SQLTablecheckOperator()
102 | SQLColumnCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
103 | _convert_to_float_if_possible()
@@ -254,7 +254,7 @@ AIR302_common_sql.py:102:1: AIR302 `airflow.operators.sql.SQLColumnCheckOperator
AIR302_common_sql.py:103:1: AIR302 `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0;
|
101 | SQLTableCheckOperator()
101 | SQLTablecheckOperator()
102 | SQLColumnCheckOperator()
103 | _convert_to_float_if_possible()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---

View File

@@ -1,394 +1,337 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
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.
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.
|
26 | # airflow
27 | DatasetFromRoot()
22 | # airflow
23 | DatasetFromRoot()
| ^^^^^^^^^^^^^^^ AIR311
28 |
29 | # airflow.datasets
24 |
25 | # airflow.datasets
|
= help: Use `airflow.sdk.Asset` instead
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 Asset
25 26 |
26 27 | # airflow
27 |-DatasetFromRoot()
28 |+Asset()
28 29 |
29 30 | # airflow.datasets
30 31 | Dataset()
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()
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.
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.
|
29 | # airflow.datasets
30 | Dataset()
25 | # airflow.datasets
26 | Dataset()
| ^^^^^^^ AIR311
31 | DatasetAlias()
32 | DatasetAll()
27 | DatasetAlias()
28 | DatasetAll()
|
= help: Use `airflow.sdk.Asset` instead
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 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()
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()
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.
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.
|
29 | # airflow.datasets
30 | Dataset()
31 | DatasetAlias()
25 | # airflow.datasets
26 | Dataset()
27 | DatasetAlias()
| ^^^^^^^^^^^^ AIR311
32 | DatasetAll()
33 | DatasetAny()
28 | DatasetAll()
29 | DatasetAny()
|
= help: Use `airflow.sdk.AssetAlias` instead
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 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()
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()
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.
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.
|
30 | Dataset()
31 | DatasetAlias()
32 | DatasetAll()
26 | Dataset()
27 | DatasetAlias()
28 | DatasetAll()
| ^^^^^^^^^^ AIR311
33 | DatasetAny()
34 | Metadata()
29 | DatasetAny()
30 | Metadata()
|
= help: Use `airflow.sdk.AssetAll` instead
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 AssetAll
25 26 |
26 27 | # airflow
27 28 | DatasetFromRoot()
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()
--------------------------------------------------------------------------------
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()
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()
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.
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.
|
31 | DatasetAlias()
32 | DatasetAll()
33 | DatasetAny()
27 | DatasetAlias()
28 | DatasetAll()
29 | DatasetAny()
| ^^^^^^^^^^ AIR311
34 | Metadata()
35 | expand_alias_to_datasets()
30 | Metadata()
31 | expand_alias_to_datasets()
|
= help: Use `airflow.sdk.AssetAny` instead
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 AssetAny
25 26 |
26 27 | # airflow
27 28 | DatasetFromRoot()
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()
--------------------------------------------------------------------------------
30 31 | Dataset()
31 32 | DatasetAlias()
32 33 | DatasetAll()
33 |-DatasetAny()
34 |+AssetAny()
34 35 | Metadata()
35 36 | expand_alias_to_datasets()
36 37 |
26 27 | Dataset()
27 28 | DatasetAlias()
28 29 | DatasetAll()
29 |-DatasetAny()
30 |+AssetAny()
30 31 | Metadata()
31 32 | expand_alias_to_datasets()
32 33 |
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.
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.
|
32 | DatasetAll()
33 | DatasetAny()
34 | Metadata()
28 | DatasetAll()
29 | DatasetAny()
30 | Metadata()
| ^^^^^^^^ AIR311
35 | expand_alias_to_datasets()
31 | expand_alias_to_datasets()
|
= help: Use `airflow.sdk.Metadata` instead
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.
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.
|
33 | DatasetAny()
34 | Metadata()
35 | expand_alias_to_datasets()
29 | DatasetAny()
30 | Metadata()
31 | expand_alias_to_datasets()
| ^^^^^^^^^^^^^^^^^^^^^^^^ AIR311
36 |
37 | # airflow.decorators
32 |
33 | # airflow.decorators
|
= help: Use `airflow.sdk.expand_alias_to_assets` instead
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 expand_alias_to_assets
25 26 |
26 27 | # airflow
27 28 | DatasetFromRoot()
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()
--------------------------------------------------------------------------------
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()
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()
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.
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.
|
37 | # airflow.decorators
38 | dag()
33 | # airflow.decorators
34 | dag()
| ^^^ AIR311
39 | task()
40 | task_group()
35 | task()
36 | task_group()
|
= help: Use `airflow.sdk.dag` instead
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.
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.
|
37 | # airflow.decorators
38 | dag()
39 | task()
33 | # airflow.decorators
34 | dag()
35 | task()
| ^^^^ AIR311
40 | task_group()
41 | setup()
36 | task_group()
37 | setup()
|
= help: Use `airflow.sdk.task` instead
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.
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.
|
38 | dag()
39 | task()
40 | task_group()
34 | dag()
35 | task()
36 | task_group()
| ^^^^^^^^^^ AIR311
41 | setup()
42 | teardown()
37 | setup()
38 | teardown()
|
= help: Use `airflow.sdk.task_group` instead
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.
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.
|
39 | task()
40 | task_group()
41 | setup()
35 | task()
36 | task_group()
37 | setup()
| ^^^^^ AIR311
42 | teardown()
38 | teardown()
|
= help: Use `airflow.sdk.setup` instead
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.
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.
|
40 | task_group()
41 | setup()
42 | teardown()
36 | task_group()
37 | setup()
38 | teardown()
| ^^^^^^^^ AIR311
43 |
44 | # airflow.io
39 |
40 | # airflow.io
|
= help: Use `airflow.sdk.teardown` instead
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.
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.
|
44 | # airflow.io
45 | ObjectStoragePath()
40 | # airflow.io
41 | ObjectStoragePath()
| ^^^^^^^^^^^^^^^^^ AIR311
46 | attach()
42 | attach()
|
= help: Use `airflow.sdk.ObjectStoragePath` instead
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.
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.
|
44 | # airflow.io
45 | ObjectStoragePath()
46 | attach()
40 | # airflow.io
41 | ObjectStoragePath()
42 | attach()
| ^^^^^^ AIR311
47 |
48 | # airflow.models
43 |
44 | # airflow.models
|
= help: Use `airflow.sdk.io.attach` instead
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.
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.
|
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()
44 | # airflow.models
45 | DAGFromModel()
| ^^^^^^^^^^^^ AIR311
51 | Variable()
46 |
47 | # airflow.models.baseoperator
|
= help: Use `airflow.sdk.DAG` instead
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.
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.
|
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()
47 | # airflow.models.baseoperator
48 | chain()
| ^^^^^ AIR311
55 | chain_linear()
56 | cross_downstream()
49 | chain_linear()
50 | cross_downstream()
|
= help: Use `airflow.sdk.chain` instead
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.
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.
|
53 | # airflow.models.baseoperator
54 | chain()
55 | chain_linear()
47 | # airflow.models.baseoperator
48 | chain()
49 | chain_linear()
| ^^^^^^^^^^^^ AIR311
56 | cross_downstream()
50 | cross_downstream()
|
= help: Use `airflow.sdk.chain_linear` instead
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.
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.
|
54 | chain()
55 | chain_linear()
56 | cross_downstream()
48 | chain()
49 | chain_linear()
50 | cross_downstream()
| ^^^^^^^^^^^^^^^^ AIR311
57 |
58 | # airflow.models.baseoperatolinker
51 |
52 | # airflow.models.baseoperatolinker
|
= help: Use `airflow.sdk.cross_downstream` instead
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.
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.
|
61 | # airflow.models.dag
62 | DAGFromDag()
55 | # airflow.models.dag
56 | DAGFromDag()
| ^^^^^^^^^^ AIR311
63 | # airflow.timetables.datasets
64 | DatasetOrTimeSchedule()
57 | # airflow.timetables.datasets
58 | DatasetOrTimeSchedule()
|
= help: Use `airflow.sdk.DAG` instead
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.
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.
|
62 | DAGFromDag()
63 | # airflow.timetables.datasets
64 | DatasetOrTimeSchedule()
56 | DAGFromDag()
57 | # airflow.timetables.datasets
58 | DatasetOrTimeSchedule()
| ^^^^^^^^^^^^^^^^^^^^^ AIR311
65 |
66 | # airflow.utils.dag_parsing_context
59 |
60 | # airflow.utils.dag_parsing_context
|
= help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead
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.timetables.assets import AssetOrTimeSchedule
25 26 |
26 27 | # airflow
27 28 | DatasetFromRoot()
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()
--------------------------------------------------------------------------------
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()
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()
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.
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.
|
66 | # airflow.utils.dag_parsing_context
67 | get_parsing_context()
60 | # airflow.utils.dag_parsing_context
61 | get_parsing_context()
| ^^^^^^^^^^^^^^^^^^^ AIR311
|
= help: Use `airflow.sdk.get_parsing_context` instead

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---

View File

@@ -100,15 +100,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) {
}
// f"select * from table where val = {val}"
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())
}
Expr::FString(f_string) => concatenated_f_string(f_string, checker.locator()),
_ => return,
};

View File

@@ -601,6 +601,4 @@ 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
|

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast};
use ruff_text_size::Ranged;
use crate::{checkers::ast::Checker, fix::edits::add_argument};
@@ -60,18 +60,10 @@ 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
@@ -98,14 +90,3 @@ 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,
})
}

View File

@@ -61,26 +61,3 @@ 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)

View File

@@ -1,7 +1,7 @@
use itertools::Itertools;
use rustc_hash::{FxBuildHasher, FxHashSet};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{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,7 +19,6 @@ use crate::fix::edits::{remove_argument, Parentheses};
/// arguments directly.
///
/// ## Example
///
/// ```python
/// def foo(bar):
/// return bar + 1
@@ -29,7 +28,6 @@ use crate::fix::edits::{remove_argument, Parentheses};
/// ```
///
/// Use instead:
///
/// ```python
/// def foo(bar):
/// return bar + 1
@@ -38,26 +36,6 @@ 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)
@@ -135,7 +113,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
.iter()
.all(|kwarg| !duplicate_keywords.contains(kwarg))
{
let edit = Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
kwargs
.iter()
.zip(dict.iter_values())
@@ -156,15 +134,7 @@ 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
},
));
)));
}
}
}

View File

@@ -208,8 +208,6 @@ 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
|
@@ -217,8 +215,6 @@ 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
@@ -228,34 +224,3 @@ 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 | )

View File

@@ -186,20 +186,12 @@ 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 => {
let mut items: Vec<&str> = value.rsplitn(split_n + 1, sep_value).collect();
items.reverse();
items
}
Direction::Right => value.rsplitn(split_n + 1, sep_value).collect(),
}
} else {
match direction {
Direction::Left => value.split(sep_value).collect(),
Direction::Right => {
let mut items: Vec<&str> = value.rsplit(sep_value).collect();
items.reverse();
items
}
Direction::Right => value.rsplit(sep_value).collect(),
}
};

View File

@@ -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,8 +854,6 @@ 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
@@ -870,39 +868,3 @@ 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))
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
|
= 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"])

View File

@@ -26,109 +26,41 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
// PTH100
["os", "path", "abspath"] => OsPathAbspath.into(),
// PTH101
["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()
}
["os", "chmod"] => OsChmod.into(),
// PTH102
["os", "makedirs"] => OsMakedirs.into(),
// PTH103
["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()
}
["os", "mkdir"] => OsMkdir.into(),
// PTH104
["os", "rename"] => {
// `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are
// set to non-default values.
// are 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 is_argument_non_default(&call.arguments, "src_dir_fd", 2)
|| is_argument_non_default(&call.arguments, "dst_dir_fd", 3)
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())
{
return;
}
OsRename.into()
}
// PTH105
["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()
}
["os", "replace"] => OsReplace.into(),
// PTH106
["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()
}
["os", "rmdir"] => OsRmdir.into(),
// PTH107
["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()
}
["os", "remove"] => OsRemove.into(),
// PTH108
["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()
}
["os", "unlink"] => OsUnlink.into(),
// PTH109
["os", "getcwd"] => OsGetcwd.into(),
["os", "getcwdb"] => OsGetcwd.into(),
@@ -144,17 +76,10 @@ 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_argument_value("path", 0)
.find_positional(0)
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|| is_argument_non_default(&call.arguments, "dir_fd", 1)
{
return;
}
@@ -223,10 +148,13 @@ 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("file", 0)
.find_argument_value("opener", 7)
.is_some_and(|expr| !expr.is_none_literal_expr())
|| call
.arguments
.find_positional(0)
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
{
return;
@@ -236,53 +164,17 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
// PTH124
["py", "path", "local"] => PyPath.into(),
// PTH207
["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()
["glob", "glob"] => Glob {
function: "glob".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(),
["glob", "iglob"] => Glob {
function: "iglob".to_string(),
}
.into(),
// PTH115
// Python 3.9+
["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()
}
["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => OsReadlink.into(),
// PTH208
["os", "listdir"] => {
if call
@@ -332,10 +224,3 @@ 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())
}

View File

@@ -1,5 +1,6 @@
---
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`
|
@@ -25,6 +26,4 @@ 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
|

View File

@@ -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 = [] # comment should be protected
147 | tmp = 1; result = [] # commment 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 # comment should be protected
154 | result = []; tmp = 1 # commment should be protected
155 | for i in range(10):
156 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401

View File

@@ -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 = [] # comment should be protected
147 | tmp = 1; result = [] # commment 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 = [] # comment should be protected
147 |- tmp = 1; result = [] # commment should be protected
148 |- for i in range(10):
149 |- result.append(i + 1) # PERF401
147 |+ tmp = 1 # comment should be protected
147 |+ tmp = 1 # commment 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 # comment should be protected
154 | result = []; tmp = 1 # commment 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 # comment should be protected
154 |- result = []; tmp = 1 # commment should be protected
155 |- for i in range(10):
156 |- result.append(i + 1) # PERF401
154 |+ tmp = 1 # comment should be protected
154 |+ tmp = 1 # commment should be protected
155 |+ result = [i + 1 for i in range(10)] # PERF401
157 156 |
158 157 |

View File

@@ -38,11 +38,6 @@ use crate::checkers::ast::Checker;
/// nums.add(num + 5)
/// ```
///
/// ## Fix safety
/// This fix is always unsafe because it changes the programs 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)]

View File

@@ -21,7 +21,6 @@ pub(crate) enum MinMax {
/// readability.
///
/// ## Example
///
/// ```python
/// minimum = min(1, 2, min(3, 4, 5))
/// maximum = max(1, 2, max(3, 4, 5))
@@ -29,26 +28,12 @@ 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)

View File

@@ -28,17 +28,6 @@ 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 platforms 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")

View File

@@ -12,11 +12,6 @@ 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

View File

@@ -89,15 +89,4 @@ 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(())
}
}

View File

@@ -1,7 +1,6 @@
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp};
use ruff_source_file::find_newline;
use ruff_python_ast::{self as ast, Expr, ExprCall, Number};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -25,11 +24,6 @@ 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>,
@@ -127,23 +121,20 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs
return;
}
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 {
arg
};
// Generate a replacement, if possible.
let replacement = if matches!(
arg,
Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_)
) {
let inner_source = checker.locator().slice(arg);
let applicability = if matches!(maybe_number, Expr::NumberLiteral(_)) {
Applicability::Safe
} else {
Applicability::DisplayOnly
};
let quote = checker.stylist().quote();
let shorthand = base.shorthand();
let replacement = try_create_replacement(checker, arg, base);
Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}"))
} else {
None
};
let mut diagnostic = Diagnostic::new(
FStringNumberFormat {
@@ -154,54 +145,15 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs
);
if let Some(replacement) = replacement {
let edit = Edit::range_replacement(replacement, subscript.range());
diagnostic.set_fix(Fix::applicable_edit(edit, applicability));
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
replacement,
subscript.range(),
)));
}
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,

View File

@@ -1,288 +1,144 @@
---
source: crates/ruff_linter/src/rules/refurb/mod.rs
---
FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"`
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}"`
|
7 | return num
8 |
9 | print(oct(num)[2:]) # FURB116
6 | print(oct(num)[2:]) # FURB116
7 | print(hex(num)[2:]) # FURB116
8 | print(bin(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
9 |
10 | 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
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
FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"`
FURB116.py:10:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"`
|
11 | print(bin(num)[2:]) # FURB116
12 |
13 | print(oct(1337)[2:]) # FURB116
8 | print(bin(num)[2:]) # FURB116
9 |
10 | print(oct(1337)[2:]) # FURB116
| ^^^^^^^^^^^^^ FURB116
14 | print(hex(1337)[2:]) # FURB116
15 | print(bin(1337)[2:]) # FURB116
11 | print(hex(1337)[2:]) # FURB116
12 | 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
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 |
FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"`
FURB116.py:11:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"`
|
13 | print(oct(1337)[2:]) # FURB116
14 | print(hex(1337)[2:]) # FURB116
10 | print(oct(1337)[2:]) # FURB116
11 | print(hex(1337)[2:]) # FURB116
| ^^^^^^^^^^^^^ FURB116
15 | print(bin(1337)[2:]) # FURB116
16 | print(bin(+1337)[2:]) # FURB116
12 | 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 |
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)
FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"`
FURB116.py:12: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
10 | print(oct(1337)[2:]) # FURB116
11 | print(hex(1337)[2:]) # FURB116
12 | print(bin(1337)[2:]) # FURB116
| ^^^^^^^^^^^^^ FURB116
16 | print(bin(+1337)[2:]) # FURB116
13 |
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
|
= 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)
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)
FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"`
FURB116.py:14:7: FURB116 Replace `bin` call with f-string
|
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)
12 | print(bin(1337)[2:]) # FURB116
13 |
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
| ^^^^^^^^^^^^^^^^^^^^^ FURB116
19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
15 | 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
FURB116.py:15: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)
14 | print(bin(return_num())[2:]) # FURB116 (no autofix)
15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix)
| ^^^^^^^^^^^^^^^^^^^^^^ FURB116
20 |
21 | ## invalid
16 |
17 | ## 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}")

Some files were not shown because too many files have changed in this diff Show More