mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-17 19:27:11 +00:00
Merge branch 'main' into unsupported-operator-diagnostics
* main: (188 commits) [ty] Discover site-packages from the environment that ty is installed in (#21286) [ty] Make special cases for `UnionType` slightly narrower (#21276) Require ignore 0.4.24 in `Cargo.toml` (#21292) [ty] Favour imported symbols over builtin symbols (#21285) docs: revise Ruff setup instructions for Zed editor (#20935) [ty] Update salsa (#21281) [syntax-error]: no binding for nonlocal PLE0117 as a semantic syntax error (#21032) [ty] Constraining a typevar with itself (possibly via union or intersection) (#21273) [`ruff`] Fix false positives on starred arguments (`RUF057`) (#21256) [ty] Simplify unions containing multiple type variables during inference (#21275) [ty] Add `ty_server::Db` trait (#21241) [ty] Refactor `Range` to/from `TextRange` conversion as prep for notebook support (#21230) [ty] Fix playground crash when file name includes path separator (#21151) [`refurb`] Fix false negative for underscores before sign in `Decimal` constructor (`FURB157`) (#21190) [ty] Allow values of type `None` in type expressions (#21263) Run codspeed benchmarks with `profiling` profile (#21261) [ty] Update expected diagnostic count in benchmarks (#21269) Avoid extra parentheses for long `match` patterns with `as` captures (#21176) [ty] Update salsa (#21265) [ty] `dict` is not assignable to `TypedDict` (#21238) ...
This commit is contained in:
commit
11935535f3
1295 changed files with 23273 additions and 9150 deletions
109
.github/workflows/ci.yaml
vendored
109
.github/workflows/ci.yaml
vendored
|
|
@ -256,15 +256,15 @@ jobs:
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
with:
|
with:
|
||||||
enable-cache: "true"
|
enable-cache: "true"
|
||||||
- name: ty mdtests (GitHub annotations)
|
- name: ty mdtests (GitHub annotations)
|
||||||
|
|
@ -277,8 +277,8 @@ jobs:
|
||||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||||
# Dogfood ty on py-fuzzer
|
- name: Dogfood ty on py-fuzzer
|
||||||
- run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
|
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
|
||||||
# Check for broken links in the documentation.
|
# Check for broken links in the documentation.
|
||||||
- run: cargo doc --all --no-deps
|
- run: cargo doc --all --no-deps
|
||||||
env:
|
env:
|
||||||
|
|
@ -320,15 +320,15 @@ jobs:
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
with:
|
with:
|
||||||
enable-cache: "true"
|
enable-cache: "true"
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
|
|
@ -353,11 +353,11 @@ jobs:
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
with:
|
with:
|
||||||
enable-cache: "true"
|
enable-cache: "true"
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
|
|
@ -378,7 +378,7 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
@ -437,8 +437,10 @@ jobs:
|
||||||
workspaces: "fuzz -> target"
|
workspaces: "fuzz -> target"
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
- name: "Install mold"
|
||||||
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo-binstall"
|
- name: "Install cargo-binstall"
|
||||||
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
uses: cargo-bins/cargo-binstall@b3f755e95653da9a2d25b99154edfdbd5b356d0a # v1.15.10
|
||||||
- name: "Install cargo-fuzz"
|
- name: "Install cargo-fuzz"
|
||||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||||
|
|
@ -458,7 +460,7 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
name: Download Ruff binary to test
|
name: Download Ruff binary to test
|
||||||
id: download-cached-binary
|
id: download-cached-binary
|
||||||
|
|
@ -494,7 +496,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup component add rustfmt
|
run: rustup component add rustfmt
|
||||||
# Run all code generation scripts, and verify that the current output is
|
# Run all code generation scripts, and verify that the current output is
|
||||||
|
|
@ -529,10 +531,9 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
with:
|
with:
|
||||||
# TODO: figure out why `ruff-ecosystem` crashes on Python 3.14
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
python-version: "3.13"
|
|
||||||
activate-environment: true
|
activate-environment: true
|
||||||
|
|
||||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
|
|
@ -646,36 +647,36 @@ jobs:
|
||||||
name: "Fuzz for new ty panics"
|
name: "Fuzz for new ty panics"
|
||||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||||
needs:
|
needs:
|
||||||
- cargo-test-linux
|
|
||||||
- determine_changes
|
- determine_changes
|
||||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
|
||||||
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 5 || 20 }}
|
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
name: Download new ty binary
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
id: ty-new
|
- name: "Install Rust toolchain"
|
||||||
with:
|
run: rustup show
|
||||||
name: ty
|
- name: "Install mold"
|
||||||
path: target/debug
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
|
||||||
name: Download baseline ty binary
|
|
||||||
with:
|
|
||||||
name: ty
|
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
|
||||||
workflow: "ci.yaml"
|
|
||||||
check_artifacts: true
|
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
||||||
- name: Fuzz
|
- name: Fuzz
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
NEW_TY: ${{ steps.ty-new.outputs.download-path }}
|
|
||||||
run: |
|
run: |
|
||||||
# Make executable, since artifact download doesn't preserve this
|
echo "new commit"
|
||||||
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
|
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||||
|
cargo build --profile=profiling --bin=ty
|
||||||
|
mv target/profiling/ty ty-new
|
||||||
|
|
||||||
|
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||||
|
git checkout -b old_commit "$MERGE_BASE"
|
||||||
|
echo "old commit (merge base)"
|
||||||
|
git rev-list --format=%s --max-count=1 old_commit
|
||||||
|
cargo build --profile=profiling --bin=ty
|
||||||
|
mv target/profiling/ty ty-old
|
||||||
|
|
||||||
(
|
(
|
||||||
uv run \
|
uv run \
|
||||||
|
|
@ -683,8 +684,8 @@ jobs:
|
||||||
--project=./python/py-fuzzer \
|
--project=./python/py-fuzzer \
|
||||||
--locked \
|
--locked \
|
||||||
fuzz \
|
fuzz \
|
||||||
--test-executable="${NEW_TY}/ty" \
|
--test-executable=ty-new \
|
||||||
--baseline-executable="${PWD}/ty" \
|
--baseline-executable=ty-old \
|
||||||
--only-new-bugs \
|
--only-new-bugs \
|
||||||
--bin=ty \
|
--bin=ty \
|
||||||
0-1000
|
0-1000
|
||||||
|
|
@ -699,7 +700,7 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
- uses: cargo-bins/cargo-binstall@b3f755e95653da9a2d25b99154edfdbd5b356d0a # v1.15.10
|
||||||
- run: cargo binstall --no-confirm cargo-shear
|
- run: cargo binstall --no-confirm cargo-shear
|
||||||
- run: cargo shear
|
- run: cargo shear
|
||||||
|
|
||||||
|
|
@ -712,10 +713,12 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
- name: "Install mold"
|
||||||
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Run ty completion evaluation"
|
- name: "Run ty completion evaluation"
|
||||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
|
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
|
||||||
- name: "Ensure there are no changes"
|
- name: "Ensure there are no changes"
|
||||||
|
|
@ -757,9 +760,9 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
- name: "Cache pre-commit"
|
- name: "Cache pre-commit"
|
||||||
|
|
@ -797,7 +800,7 @@ jobs:
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
with:
|
with:
|
||||||
python-version: 3.13
|
python-version: 3.13
|
||||||
activate-environment: true
|
activate-environment: true
|
||||||
|
|
@ -901,7 +904,7 @@ jobs:
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
@ -939,18 +942,18 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
- name: "Build benchmarks"
|
- name: "Build benchmarks"
|
||||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||||
|
|
@ -977,18 +980,18 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
- name: "Build benchmarks"
|
- name: "Build benchmarks"
|
||||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
|
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||||
|
|
@ -1015,18 +1018,18 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- name: "Install codspeed"
|
- name: "Install codspeed"
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-codspeed
|
tool: cargo-codspeed
|
||||||
|
|
||||||
- name: "Build benchmarks"
|
- name: "Build benchmarks"
|
||||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
|
||||||
|
|
||||||
- name: "Run benchmarks"
|
- name: "Run benchmarks"
|
||||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||||
|
|
|
||||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
|
|
|
||||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
with:
|
with:
|
||||||
|
|
@ -85,7 +85,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
7
.github/workflows/publish-playground.yml
vendored
7
.github/workflows/publish-playground.yml
vendored
|
|
@ -18,6 +18,8 @@ env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUSTUP_MAX_RETRIES: 10
|
RUSTUP_MAX_RETRIES: 10
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -29,11 +31,10 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "npm"
|
package-manager-cache: false
|
||||||
cache-dependency-path: playground/package-lock.json
|
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
- name: "Install Node dependencies"
|
- name: "Install Node dependencies"
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
|
||||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
pattern: wheels-*
|
pattern: wheels-*
|
||||||
|
|
|
||||||
3
.github/workflows/publish-ty-playground.yml
vendored
3
.github/workflows/publish-ty-playground.yml
vendored
|
|
@ -35,9 +35,10 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
package-manager-cache: false
|
||||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||||
- name: "Install Node dependencies"
|
- name: "Install Node dependencies"
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
|
||||||
2
.github/workflows/publish-wasm.yml
vendored
2
.github/workflows/publish-wasm.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json crates/ruff_wasm/pkg
|
mv /tmp/package.json crates/ruff_wasm/pkg
|
||||||
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
|
||||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
# This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist
|
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
|
||||||
#
|
#
|
||||||
# Copyright 2022-2024, axodotdev
|
# Copyright 2022-2024, axodotdev
|
||||||
# Copyright 2025 Astral Software Inc.
|
|
||||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||||
#
|
#
|
||||||
# CI that:
|
# CI that:
|
||||||
|
|
@ -69,9 +68,9 @@ jobs:
|
||||||
# we specify bash to get pipefail; it guards against the `curl` command
|
# we specify bash to get pipefail; it guards against the `curl` command
|
||||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||||
shell: bash
|
shell: bash
|
||||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
|
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.0/cargo-dist-installer.sh | sh"
|
||||||
- name: Cache dist
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/dist
|
path: ~/.cargo/bin/dist
|
||||||
|
|
@ -87,7 +86,7 @@ jobs:
|
||||||
cat plan-dist-manifest.json
|
cat plan-dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||||
with:
|
with:
|
||||||
name: artifacts-plan-dist-manifest
|
name: artifacts-plan-dist-manifest
|
||||||
path: plan-dist-manifest.json
|
path: plan-dist-manifest.json
|
||||||
|
|
@ -129,14 +128,14 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
|
|
@ -154,7 +153,7 @@ jobs:
|
||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-global
|
name: artifacts-build-global
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -180,14 +179,14 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Fetch artifacts from scratch-storage
|
# Fetch artifacts from scratch-storage
|
||||||
- name: Fetch artifacts
|
- name: Fetch artifacts
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
|
|
@ -201,7 +200,7 @@ jobs:
|
||||||
cat dist-manifest.json
|
cat dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||||
with:
|
with:
|
||||||
# Overwrite the previous copy
|
# Overwrite the previous copy
|
||||||
name: artifacts-dist-manifest
|
name: artifacts-dist-manifest
|
||||||
|
|
@ -257,7 +256,7 @@ jobs:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
# Create a GitHub Release while uploading all files to it
|
# Create a GitHub Release while uploading all files to it
|
||||||
- name: "Download GitHub Artifacts"
|
- name: "Download GitHub Artifacts"
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
|
||||||
10
.github/workflows/sync_typeshed.yaml
vendored
10
.github/workflows/sync_typeshed.yaml
vendored
|
|
@ -77,7 +77,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name typeshedbot
|
git config --global user.name typeshedbot
|
||||||
git config --global user.email '<>'
|
git config --global user.email '<>'
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- name: Sync typeshed stubs
|
- name: Sync typeshed stubs
|
||||||
run: |
|
run: |
|
||||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||||
|
|
@ -131,7 +131,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- name: Setup git
|
- name: Setup git
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name typeshedbot
|
git config --global user.name typeshedbot
|
||||||
|
|
@ -170,7 +170,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
- name: Setup git
|
- name: Setup git
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name typeshedbot
|
git config --global user.name typeshedbot
|
||||||
|
|
@ -207,12 +207,12 @@ jobs:
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo nextest"
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-nextest
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- name: Update snapshots
|
- name: Update snapshots
|
||||||
|
|
|
||||||
5
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
5
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
|
|
@ -33,11 +33,14 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
with:
|
||||||
|
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
|
||||||
5
.github/workflows/ty-ecosystem-report.yaml
vendored
5
.github/workflows/ty-ecosystem-report.yaml
vendored
|
|
@ -29,11 +29,14 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||||
|
with:
|
||||||
|
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
with:
|
with:
|
||||||
workspaces: "ruff"
|
workspaces: "ruff"
|
||||||
|
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
|
||||||
2
.github/workflows/typing_conformance.yaml
vendored
2
.github/workflows/typing_conformance.yaml
vendored
|
|
@ -24,7 +24,7 @@ env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUSTUP_MAX_RETRIES: 10
|
RUSTUP_MAX_RETRIES: 10
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
CONFORMANCE_SUITE_COMMIT: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
|
CONFORMANCE_SUITE_COMMIT: 9f6d8ced7cd1c8d92687a4e9c96d7716452e471e
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
typing_conformance:
|
typing_conformance:
|
||||||
|
|
|
||||||
15
.github/zizmor.yml
vendored
15
.github/zizmor.yml
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||||
# https://woodruffw.github.io/zizmor/configuration/
|
# https://docs.zizmor.sh/configuration/
|
||||||
#
|
#
|
||||||
# TODO: can we remove the ignores here so that our workflows are more secure?
|
# TODO: can we remove the ignores here so that our workflows are more secure?
|
||||||
rules:
|
rules:
|
||||||
|
|
@ -9,13 +9,18 @@ rules:
|
||||||
cache-poisoning:
|
cache-poisoning:
|
||||||
ignore:
|
ignore:
|
||||||
- build-docker.yml
|
- build-docker.yml
|
||||||
- publish-playground.yml
|
|
||||||
- ty-ecosystem-analyzer.yaml
|
|
||||||
- ty-ecosystem-report.yaml
|
|
||||||
excessive-permissions:
|
excessive-permissions:
|
||||||
# it's hard to test what the impact of removing these ignores would be
|
# it's hard to test what the impact of removing these ignores would be
|
||||||
# without actually running the release workflow...
|
# without actually running the release workflow...
|
||||||
ignore:
|
ignore:
|
||||||
- build-docker.yml
|
- build-docker.yml
|
||||||
- publish-playground.yml
|
|
||||||
- publish-docs.yml
|
- publish-docs.yml
|
||||||
|
secrets-inherit:
|
||||||
|
# `cargo dist` makes extensive use of `secrets: inherit`,
|
||||||
|
# and we can't easily fix that until an upstream release changes that.
|
||||||
|
disable: true
|
||||||
|
template-injection:
|
||||||
|
ignore:
|
||||||
|
# like with `secrets-inherit`, `cargo dist` introduces some
|
||||||
|
# template injections. We've manually audited these usages for safety.
|
||||||
|
- release.yml
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ repos:
|
||||||
|
|
||||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||||
rev: v1.11.0
|
rev: v1.16.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|
|
||||||
103
CHANGELOG.md
103
CHANGELOG.md
|
|
@ -1,5 +1,108 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.14.3
|
||||||
|
|
||||||
|
Released on 2025-10-30.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
|
||||||
|
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
|
||||||
|
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
|
||||||
|
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
|
||||||
|
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
|
||||||
|
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
|
||||||
|
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
|
||||||
|
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
|
||||||
|
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
|
||||||
|
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
|
||||||
|
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
|
||||||
|
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
|
||||||
|
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
|
||||||
|
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
|
||||||
|
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@danparizher](https://github.com/danparizher)
|
||||||
|
- [@jvacek](https://github.com/jvacek)
|
||||||
|
- [@ntBre](https://github.com/ntBre)
|
||||||
|
- [@augustelalande](https://github.com/augustelalande)
|
||||||
|
- [@prakhar1144](https://github.com/prakhar1144)
|
||||||
|
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||||
|
- [@dylwil3](https://github.com/dylwil3)
|
||||||
|
- [@fatelei](https://github.com/fatelei)
|
||||||
|
- [@ShaharNaveh](https://github.com/ShaharNaveh)
|
||||||
|
- [@Lee-W](https://github.com/Lee-W)
|
||||||
|
|
||||||
|
## 0.14.2
|
||||||
|
|
||||||
|
Released on 2025-10-23.
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`flake8-gettext`\] Resolve qualified names and built-in bindings (`INT001`, `INT002`, `INT003`) ([#19045](https://github.com/astral-sh/ruff/pull/19045))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Avoid reusing nested, interpolated quotes before Python 3.12 ([#20930](https://github.com/astral-sh/ruff/pull/20930))
|
||||||
|
- Catch syntax errors in nested interpolations before Python 3.12 ([#20949](https://github.com/astral-sh/ruff/pull/20949))
|
||||||
|
- \[`fastapi`\] Handle ellipsis defaults in `FAST002` autofix ([#20810](https://github.com/astral-sh/ruff/pull/20810))
|
||||||
|
- \[`flake8-simplify`\] Skip `SIM911` when unknown arguments are present ([#20697](https://github.com/astral-sh/ruff/pull/20697))
|
||||||
|
- \[`pyupgrade`\] Always parenthesize assignment expressions in fix for `f-string` (`UP032`) ([#21003](https://github.com/astral-sh/ruff/pull/21003))
|
||||||
|
- \[`pyupgrade`\] Fix `UP032` conversion for decimal ints with underscores ([#21022](https://github.com/astral-sh/ruff/pull/21022))
|
||||||
|
- \[`fastapi`\] Skip autofix for keyword and `__debug__` path params (`FAST003`) ([#20960](https://github.com/astral-sh/ruff/pull/20960))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`flake8-bugbear`\] Skip `B905` and `B912` for fewer than two iterables and no starred arguments ([#20998](https://github.com/astral-sh/ruff/pull/20998))
|
||||||
|
- \[`ruff`\] Use `DiagnosticTag` for more `pyflakes` and `pandas` rules ([#20801](https://github.com/astral-sh/ruff/pull/20801))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Improve JSON output from `ruff rule` ([#20168](https://github.com/astral-sh/ruff/pull/20168))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add source to testimonial ([#20971](https://github.com/astral-sh/ruff/pull/20971))
|
||||||
|
- Document when a rule was added ([#21035](https://github.com/astral-sh/ruff/pull/21035))
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- [syntax-errors] Name is parameter and global ([#20426](https://github.com/astral-sh/ruff/pull/20426))
|
||||||
|
- [syntax-errors] Alternative `match` patterns bind different names ([#20682](https://github.com/astral-sh/ruff/pull/20682))
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [@hengky-kurniawan-1](https://github.com/hengky-kurniawan-1)
|
||||||
|
- [@ShalokShalom](https://github.com/ShalokShalom)
|
||||||
|
- [@robsdedude](https://github.com/robsdedude)
|
||||||
|
- [@LoicRiegel](https://github.com/LoicRiegel)
|
||||||
|
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||||
|
- [@dylwil3](https://github.com/dylwil3)
|
||||||
|
- [@11happy](https://github.com/11happy)
|
||||||
|
- [@ntBre](https://github.com/ntBre)
|
||||||
|
|
||||||
## 0.14.1
|
## 0.14.1
|
||||||
|
|
||||||
Released on 2025-10-16.
|
Released on 2025-10-16.
|
||||||
|
|
|
||||||
417
Cargo.lock
generated
417
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -5,7 +5,7 @@ resolver = "2"
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
# Please update rustfmt.toml when bumping the Rust edition
|
# Please update rustfmt.toml when bumping the Rust edition
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.88"
|
rust-version = "1.89"
|
||||||
homepage = "https://docs.astral.sh/ruff"
|
homepage = "https://docs.astral.sh/ruff"
|
||||||
documentation = "https://docs.astral.sh/ruff"
|
documentation = "https://docs.astral.sh/ruff"
|
||||||
repository = "https://github.com/astral-sh/ruff"
|
repository = "https://github.com/astral-sh/ruff"
|
||||||
|
|
@ -84,7 +84,7 @@ dashmap = { version = "6.0.1" }
|
||||||
dir-test = { version = "0.4.0" }
|
dir-test = { version = "0.4.0" }
|
||||||
dunce = { version = "1.0.5" }
|
dunce = { version = "1.0.5" }
|
||||||
drop_bomb = { version = "0.1.5" }
|
drop_bomb = { version = "0.1.5" }
|
||||||
etcetera = { version = "0.10.0" }
|
etcetera = { version = "0.11.0" }
|
||||||
fern = { version = "0.7.0" }
|
fern = { version = "0.7.0" }
|
||||||
filetime = { version = "0.2.23" }
|
filetime = { version = "0.2.23" }
|
||||||
getrandom = { version = "0.3.1" }
|
getrandom = { version = "0.3.1" }
|
||||||
|
|
@ -103,7 +103,7 @@ hashbrown = { version = "0.16.0", default-features = false, features = [
|
||||||
"inline-more",
|
"inline-more",
|
||||||
] }
|
] }
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
ignore = { version = "0.4.22" }
|
ignore = { version = "0.4.24" }
|
||||||
imara-diff = { version = "0.1.5" }
|
imara-diff = { version = "0.1.5" }
|
||||||
imperative = { version = "1.0.4" }
|
imperative = { version = "1.0.4" }
|
||||||
indexmap = { version = "2.6.0" }
|
indexmap = { version = "2.6.0" }
|
||||||
|
|
@ -124,7 +124,7 @@ lsp-server = { version = "0.7.6" }
|
||||||
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
|
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
|
||||||
"proposed",
|
"proposed",
|
||||||
] }
|
] }
|
||||||
matchit = { version = "0.8.1" }
|
matchit = { version = "0.9.0" }
|
||||||
memchr = { version = "2.7.1" }
|
memchr = { version = "2.7.1" }
|
||||||
mimalloc = { version = "0.1.39" }
|
mimalloc = { version = "0.1.39" }
|
||||||
natord = { version = "1.0.9" }
|
natord = { version = "1.0.9" }
|
||||||
|
|
@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" }
|
||||||
rustc-hash = { version = "2.0.0" }
|
rustc-hash = { version = "2.0.0" }
|
||||||
rustc-stable-hash = { version = "0.1.2" }
|
rustc-stable-hash = { version = "0.1.2" }
|
||||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef9f9329be6923acd050c8dddd172e3bc93e8051", default-features = false, features = [
|
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "05a9af7f554b64b8aadc2eeb6f2caf73d0408d09", default-features = false, features = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"macros",
|
"macros",
|
||||||
"salsa_unstable",
|
"salsa_unstable",
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||||
|
|
||||||
# For a specific version.
|
# For a specific version.
|
||||||
curl -LsSf https://astral.sh/ruff/0.14.1/install.sh | sh
|
curl -LsSf https://astral.sh/ruff/0.14.3/install.sh | sh
|
||||||
powershell -c "irm https://astral.sh/ruff/0.14.1/install.ps1 | iex"
|
powershell -c "irm https://astral.sh/ruff/0.14.3/install.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||||
|
|
@ -181,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.1
|
rev: v0.14.3
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.1"
|
version = "0.14.3"
|
||||||
publish = true
|
publish = true
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use path_absolutize::CWD;
|
||||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_linter::{warn_user, warn_user_once};
|
use ruff_linter::{warn_user, warn_user_once};
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
|
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
|
||||||
|
|
@ -127,10 +128,6 @@ pub(crate) fn analyze_graph(
|
||||||
},
|
},
|
||||||
Some(language) => PySourceType::from(language),
|
Some(language) => PySourceType::from(language),
|
||||||
};
|
};
|
||||||
if matches!(source_type, PySourceType::Ipynb) {
|
|
||||||
debug!("Ignoring Jupyter notebook: {}", path.display());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to system paths.
|
// Convert to system paths.
|
||||||
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
|
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
|
||||||
|
|
@ -147,13 +144,34 @@ pub(crate) fn analyze_graph(
|
||||||
let root = root.clone();
|
let root = root.clone();
|
||||||
let result = inner_result.clone();
|
let result = inner_result.clone();
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
|
// Extract source code (handles both .py and .ipynb files)
|
||||||
|
let source_kind = match SourceKind::from_path(path.as_std_path(), source_type) {
|
||||||
|
Ok(Some(source_kind)) => source_kind,
|
||||||
|
Ok(None) => {
|
||||||
|
debug!("Skipping non-Python notebook: {path}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Failed to read source for {path}: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_code = source_kind.source_code();
|
||||||
|
|
||||||
// Identify any imports via static analysis.
|
// Identify any imports via static analysis.
|
||||||
let mut imports =
|
let mut imports = ModuleImports::detect(
|
||||||
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
|
&db,
|
||||||
.unwrap_or_else(|err| {
|
source_code,
|
||||||
warn!("Failed to generate import map for {path}: {err}");
|
source_type,
|
||||||
ModuleImports::default()
|
&path,
|
||||||
});
|
package.as_deref(),
|
||||||
|
string_imports,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
warn!("Failed to generate import map for {path}: {err}");
|
||||||
|
ModuleImports::default()
|
||||||
|
});
|
||||||
|
|
||||||
debug!("Discovered {} imports for {}", imports.len(), path);
|
debug!("Discovered {} imports for {}", imports.len(), path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,7 @@ pub(crate) fn format_source(
|
||||||
let line_index = LineIndex::from_source_text(unformatted);
|
let line_index = LineIndex::from_source_text(unformatted);
|
||||||
let byte_range = range.to_text_range(unformatted, &line_index);
|
let byte_range = range.to_text_range(unformatted, &line_index);
|
||||||
format_range(unformatted, byte_range, options).map(|formatted_range| {
|
format_range(unformatted, byte_range, options).map(|formatted_range| {
|
||||||
let mut formatted = unformatted.to_string();
|
let mut formatted = unformatted.clone();
|
||||||
formatted.replace_range(
|
formatted.replace_range(
|
||||||
std::ops::Range::<usize>::from(formatted_range.source_range()),
|
std::ops::Range::<usize>::from(formatted_range.source_range()),
|
||||||
formatted_range.as_code(),
|
formatted_range.as_code(),
|
||||||
|
|
@ -879,19 +879,7 @@ impl From<&FormatCommandError> for Diagnostic {
|
||||||
| FormatCommandError::Write(_, source_error) => {
|
| FormatCommandError::Write(_, source_error) => {
|
||||||
Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error)
|
Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error)
|
||||||
}
|
}
|
||||||
FormatCommandError::Format(_, format_module_error) => match format_module_error {
|
FormatCommandError::Format(_, format_module_error) => format_module_error.into(),
|
||||||
FormatModuleError::ParseError(parse_error) => Diagnostic::new(
|
|
||||||
DiagnosticId::InternalError,
|
|
||||||
Severity::Error,
|
|
||||||
&parse_error.error,
|
|
||||||
),
|
|
||||||
FormatModuleError::FormatError(format_error) => {
|
|
||||||
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, format_error)
|
|
||||||
}
|
|
||||||
FormatModuleError::PrintError(print_error) => {
|
|
||||||
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, print_error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new(
|
FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new(
|
||||||
DiagnosticId::InvalidCliOption,
|
DiagnosticId::InvalidCliOption,
|
||||||
Severity::Error,
|
Severity::Error,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ struct Explanation<'a> {
|
||||||
explanation: Option<&'a str>,
|
explanation: Option<&'a str>,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
status: RuleGroup,
|
status: RuleGroup,
|
||||||
|
source_location: SourceLocation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Explanation<'a> {
|
impl<'a> Explanation<'a> {
|
||||||
|
|
@ -43,6 +44,10 @@ impl<'a> Explanation<'a> {
|
||||||
explanation: rule.explanation(),
|
explanation: rule.explanation(),
|
||||||
preview: rule.is_preview(),
|
preview: rule.is_preview(),
|
||||||
status: rule.group(),
|
status: rule.group(),
|
||||||
|
source_location: SourceLocation {
|
||||||
|
file: rule.file(),
|
||||||
|
line: rule.line(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,3 +132,14 @@ pub(crate) fn rules(format: HelpFormat) -> Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The location of the rule's implementation in the Ruff source tree, relative to the repository
|
||||||
|
/// root.
|
||||||
|
///
|
||||||
|
/// For most rules this will point to the `#[derive(ViolationMetadata)]` line above the rule's
|
||||||
|
/// struct.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SourceLocation {
|
||||||
|
file: &'static str,
|
||||||
|
line: u32,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ use itertools::{Itertools, iterate};
|
||||||
use ruff_linter::linter::FixTable;
|
use ruff_linter::linter::FixTable;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use ruff_db::diagnostic::{
|
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, SecondaryCode};
|
||||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
|
|
||||||
};
|
|
||||||
use ruff_linter::fs::relativize_path;
|
use ruff_linter::fs::relativize_path;
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
use ruff_linter::message::{EmitterContext, render_diagnostics};
|
use ruff_linter::message::{EmitterContext, render_diagnostics};
|
||||||
|
|
@ -390,21 +388,18 @@ impl Printer {
|
||||||
|
|
||||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||||
let format = if preview {
|
let format = if preview {
|
||||||
DiagnosticFormat::Full
|
self.format
|
||||||
} else {
|
} else {
|
||||||
DiagnosticFormat::Concise
|
OutputFormat::Concise
|
||||||
};
|
};
|
||||||
let config = DisplayDiagnosticConfig::default()
|
let config = DisplayDiagnosticConfig::default()
|
||||||
|
.preview(preview)
|
||||||
.hide_severity(true)
|
.hide_severity(true)
|
||||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
|
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||||
.format(format)
|
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||||
.with_fix_applicability(self.unsafe_fixes.required_applicability());
|
.show_fix_diff(preview);
|
||||||
write!(
|
render_diagnostics(writer, format, config, &context, &diagnostics.inner)?;
|
||||||
writer,
|
|
||||||
"{}",
|
|
||||||
DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -653,3 +653,133 @@ fn venv() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn notebook_basic() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let root = ChildPath::new(tempdir.path());
|
||||||
|
|
||||||
|
root.child("ruff").child("__init__.py").write_str("")?;
|
||||||
|
root.child("ruff")
|
||||||
|
.child("a.py")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
def helper():
|
||||||
|
pass
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create a basic notebook with a simple import
|
||||||
|
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from ruff.a import helper"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python",
|
||||||
|
"version": "3.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"notebook.ipynb": [
|
||||||
|
"ruff/a.py"
|
||||||
|
],
|
||||||
|
"ruff/__init__.py": [],
|
||||||
|
"ruff/a.py": []
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn notebook_with_magic() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let root = ChildPath::new(tempdir.path());
|
||||||
|
|
||||||
|
root.child("ruff").child("__init__.py").write_str("")?;
|
||||||
|
root.child("ruff")
|
||||||
|
.child("a.py")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
def helper():
|
||||||
|
pass
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create a notebook with IPython magic commands and imports
|
||||||
|
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"%load_ext autoreload\n",
|
||||||
|
"%autoreload 2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from ruff.a import helper"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python",
|
||||||
|
"version": "3.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"notebook.ipynb": [
|
||||||
|
"ruff/a.py"
|
||||||
|
],
|
||||||
|
"ruff/__init__.py": [],
|
||||||
|
"ruff/a.py": []
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -953,7 +953,11 @@ fn rule_f401() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rule_f401_output_json() {
|
fn rule_f401_output_json() {
|
||||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
|
insta::with_settings!({filters => vec![
|
||||||
|
(r#"("file": ")[^"]+(",)"#, "$1<FILE>$2"),
|
||||||
|
]}, {
|
||||||
|
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@ exit_code: 0
|
||||||
"fix_availability": "Sometimes",
|
"fix_availability": "Sometimes",
|
||||||
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
|
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
|
||||||
"preview": false,
|
"preview": false,
|
||||||
"status": "Stable"
|
"status": {
|
||||||
|
"Stable": {
|
||||||
|
"since": "v0.0.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_location": {
|
||||||
|
"file": "<FILE>",
|
||||||
|
"line": 145
|
||||||
|
}
|
||||||
}
|
}
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
|
|
||||||
|
|
@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY313,
|
python_version: PythonVersion::PY313,
|
||||||
},
|
},
|
||||||
100,
|
110,
|
||||||
);
|
);
|
||||||
|
|
||||||
bench_project(&benchmark, criterion);
|
bench_project(&benchmark, criterion);
|
||||||
|
|
@ -684,7 +684,7 @@ fn anyio(criterion: &mut Criterion) {
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY313,
|
python_version: PythonVersion::PY313,
|
||||||
},
|
},
|
||||||
100,
|
150,
|
||||||
);
|
);
|
||||||
|
|
||||||
bench_project(&benchmark, criterion);
|
bench_project(&benchmark, criterion);
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
400,
|
525,
|
||||||
);
|
);
|
||||||
|
|
||||||
static PANDAS: Benchmark = Benchmark::new(
|
static PANDAS: Benchmark = Benchmark::new(
|
||||||
|
|
@ -210,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
100,
|
320,
|
||||||
);
|
);
|
||||||
|
|
||||||
static STATIC_FRAME: Benchmark = Benchmark::new(
|
static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||||
|
|
@ -226,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-08-09",
|
max_dep_date: "2025-08-09",
|
||||||
python_version: PythonVersion::PY311,
|
python_version: PythonVersion::PY311,
|
||||||
},
|
},
|
||||||
630,
|
800,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
|
||||||
|
|
@ -470,6 +470,11 @@ impl File {
|
||||||
self.source_type(db).is_stub()
|
self.source_type(db).is_stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the file is an `__init__.pyi`
|
||||||
|
pub fn is_package_stub(self, db: &dyn Db) -> bool {
|
||||||
|
self.path(db).as_str().ends_with("__init__.pyi")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn source_type(self, db: &dyn Db) -> PySourceType {
|
pub fn source_type(self, db: &dyn Db) -> PySourceType {
|
||||||
match self.path(db) {
|
match self.path(db) {
|
||||||
FilePath::System(path) => path
|
FilePath::System(path) => path
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,12 @@ impl System for OsSystem {
|
||||||
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
|
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
|
||||||
/// when setting [`WalkDirectoryBuilder::standard_filters`] to true.
|
/// when setting [`WalkDirectoryBuilder::standard_filters`] to true.
|
||||||
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
|
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
|
||||||
WalkDirectoryBuilder::new(path, OsDirectoryWalker {})
|
WalkDirectoryBuilder::new(
|
||||||
|
path,
|
||||||
|
OsDirectoryWalker {
|
||||||
|
cwd: self.current_directory().to_path_buf(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glob(
|
fn glob(
|
||||||
|
|
@ -454,7 +459,9 @@ struct ListedDirectory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct OsDirectoryWalker;
|
struct OsDirectoryWalker {
|
||||||
|
cwd: SystemPathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
impl DirectoryWalker for OsDirectoryWalker {
|
impl DirectoryWalker for OsDirectoryWalker {
|
||||||
fn walk(
|
fn walk(
|
||||||
|
|
@ -473,6 +480,7 @@ impl DirectoryWalker for OsDirectoryWalker {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut builder = ignore::WalkBuilder::new(first.as_std_path());
|
let mut builder = ignore::WalkBuilder::new(first.as_std_path());
|
||||||
|
builder.current_dir(self.cwd.as_std_path());
|
||||||
|
|
||||||
builder.standard_filters(standard_filters);
|
builder.standard_filters(standard_filters);
|
||||||
builder.hidden(hidden);
|
builder.hidden(hidden);
|
||||||
|
|
|
||||||
|
|
@ -723,10 +723,11 @@ impl ruff_cache::CacheKey for SystemPathBuf {
|
||||||
|
|
||||||
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
|
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
pub struct SystemVirtualPath(str);
|
pub struct SystemVirtualPath(str);
|
||||||
|
|
||||||
impl SystemVirtualPath {
|
impl SystemVirtualPath {
|
||||||
pub fn new(path: &str) -> &SystemVirtualPath {
|
pub const fn new(path: &str) -> &SystemVirtualPath {
|
||||||
// SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
|
// SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
|
||||||
// *const str to a *const SystemVirtualPath is valid.
|
// *const str to a *const SystemVirtualPath is valid.
|
||||||
unsafe { &*(path as *const str as *const SystemVirtualPath) }
|
unsafe { &*(path as *const str as *const SystemVirtualPath) }
|
||||||
|
|
@ -767,8 +768,8 @@ pub struct SystemVirtualPathBuf(String);
|
||||||
|
|
||||||
impl SystemVirtualPathBuf {
|
impl SystemVirtualPathBuf {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_path(&self) -> &SystemVirtualPath {
|
pub const fn as_path(&self) -> &SystemVirtualPath {
|
||||||
SystemVirtualPath::new(&self.0)
|
SystemVirtualPath::new(self.0.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -852,6 +853,12 @@ impl ruff_cache::CacheKey for SystemVirtualPathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Borrow<SystemVirtualPath> for SystemVirtualPathBuf {
|
||||||
|
fn borrow(&self) -> &SystemVirtualPath {
|
||||||
|
self.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Deduplicates identical paths and removes nested paths.
|
/// Deduplicates identical paths and removes nested paths.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use std::path::PathBuf;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
|
use ruff_linter::codes::RuleGroup;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use ruff_linter::FixAvailability;
|
use ruff_linter::FixAvailability;
|
||||||
|
|
@ -31,6 +32,47 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||||
|
|
||||||
let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
|
let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
|
||||||
|
|
||||||
|
let status_text = match rule.group() {
|
||||||
|
RuleGroup::Stable { since } => {
|
||||||
|
format!(
|
||||||
|
r#"Added in <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RuleGroup::Preview { since } => {
|
||||||
|
format!(
|
||||||
|
r#"Preview (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RuleGroup::Deprecated { since } => {
|
||||||
|
format!(
|
||||||
|
r#"Deprecated (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RuleGroup::Removed { since } => {
|
||||||
|
format!(
|
||||||
|
r#"Removed (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = writeln!(
|
||||||
|
&mut output,
|
||||||
|
r#"<small>
|
||||||
|
{status_text} ·
|
||||||
|
<a href="https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20(%27{encoded_name}%27%20OR%20{rule_code})" target="_blank">Related issues</a> ·
|
||||||
|
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||||
|
</small>
|
||||||
|
|
||||||
|
"#,
|
||||||
|
encoded_name =
|
||||||
|
url::form_urlencoded::byte_serialize(rule.name().as_str().as_bytes())
|
||||||
|
.collect::<String>(),
|
||||||
|
rule_code = rule.noqa_code(),
|
||||||
|
file =
|
||||||
|
url::form_urlencoded::byte_serialize(rule.file().replace('\\', "/").as_bytes())
|
||||||
|
.collect::<String>(),
|
||||||
|
line = rule.line(),
|
||||||
|
);
|
||||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||||
if linter.url().is_some() {
|
if linter.url().is_some() {
|
||||||
let common_prefix: String = match linter.common_prefix() {
|
let common_prefix: String = match linter.common_prefix() {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||||
generate_set(
|
generate_set(
|
||||||
output,
|
output,
|
||||||
Set::Named {
|
Set::Named {
|
||||||
name: set_name.to_string(),
|
name: set_name.clone(),
|
||||||
set: *sub_set,
|
set: *sub_set,
|
||||||
},
|
},
|
||||||
parents,
|
parents,
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,24 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||||
table_out.push('\n');
|
table_out.push('\n');
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
let status_token = match rule.group() {
|
let status_token = match rule.group() {
|
||||||
RuleGroup::Removed => {
|
RuleGroup::Removed { since } => {
|
||||||
format!(
|
format!(
|
||||||
"<span {SYMBOL_STYLE} title='Rule has been removed'>{REMOVED_SYMBOL}</span>"
|
"<span {SYMBOL_STYLE} title='Rule was removed in {since}'>{REMOVED_SYMBOL}</span>"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RuleGroup::Deprecated => {
|
RuleGroup::Deprecated { since } => {
|
||||||
format!(
|
format!(
|
||||||
"<span {SYMBOL_STYLE} title='Rule has been deprecated'>{WARNING_SYMBOL}</span>"
|
"<span {SYMBOL_STYLE} title='Rule has been deprecated since {since}'>{WARNING_SYMBOL}</span>"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RuleGroup::Preview => {
|
RuleGroup::Preview { since } => {
|
||||||
format!("<span {SYMBOL_STYLE} title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
format!(
|
||||||
|
"<span {SYMBOL_STYLE} title='Rule has been in preview since {since}'>{PREVIEW_SYMBOL}</span>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RuleGroup::Stable { since } => {
|
||||||
|
format!("<span {SYMBOL_STYLE} title='Rule has been stable since {since}'></span>")
|
||||||
}
|
}
|
||||||
RuleGroup::Stable => format!("<span {SYMBOL_STYLE}></span>"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fix_token = match rule.fixable() {
|
let fix_token = match rule.fixable() {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||||
generate_set(
|
generate_set(
|
||||||
output,
|
output,
|
||||||
Set::Named {
|
Set::Named {
|
||||||
name: set_name.to_string(),
|
name: set_name.clone(),
|
||||||
set: *sub_set,
|
set: *sub_set,
|
||||||
},
|
},
|
||||||
parents,
|
parents,
|
||||||
|
|
|
||||||
|
|
@ -1006,7 +1006,7 @@ impl<Context> std::fmt::Debug for Align<'_, Context> {
|
||||||
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
|
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
|
||||||
/// break before and after the content.
|
/// break before and after the content.
|
||||||
///
|
///
|
||||||
/// Doesn't create an indentation if the passed in content is [`FormatElement.is_empty`].
|
/// Doesn't create an indentation if the passed in content is empty.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,7 @@ pub trait FormatElements {
|
||||||
/// Represents the width by adding 1 to the actual width so that the width can be represented by a [`NonZeroU32`],
|
/// Represents the width by adding 1 to the actual width so that the width can be represented by a [`NonZeroU32`],
|
||||||
/// allowing [`TextWidth`] or [`Option<Width>`] fit in 4 bytes rather than 8.
|
/// allowing [`TextWidth`] or [`Option<Width>`] fit in 4 bytes rather than 8.
|
||||||
///
|
///
|
||||||
/// This means that 2^32 can not be precisely represented and instead has the same value as 2^32-1.
|
/// This means that 2^32 cannot be precisely represented and instead has the same value as 2^32-1.
|
||||||
/// This imprecision shouldn't matter in practice because either text are longer than any configured line width
|
/// This imprecision shouldn't matter in practice because either text are longer than any configured line width
|
||||||
/// and thus, the text should break.
|
/// and thus, the text should break.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
|
use ruff_python_ast::PySourceType;
|
||||||
use ruff_python_ast::helpers::to_module_path;
|
use ruff_python_ast::helpers::to_module_path;
|
||||||
use ruff_python_parser::{Mode, ParseOptions, parse};
|
use ruff_python_parser::{ParseOptions, parse};
|
||||||
|
|
||||||
use crate::collector::Collector;
|
use crate::collector::Collector;
|
||||||
pub use crate::db::ModuleDb;
|
pub use crate::db::ModuleDb;
|
||||||
|
|
@ -24,13 +25,14 @@ impl ModuleImports {
|
||||||
/// Detect the [`ModuleImports`] for a given Python file.
|
/// Detect the [`ModuleImports`] for a given Python file.
|
||||||
pub fn detect(
|
pub fn detect(
|
||||||
db: &ModuleDb,
|
db: &ModuleDb,
|
||||||
|
source: &str,
|
||||||
|
source_type: PySourceType,
|
||||||
path: &SystemPath,
|
path: &SystemPath,
|
||||||
package: Option<&SystemPath>,
|
package: Option<&SystemPath>,
|
||||||
string_imports: StringImports,
|
string_imports: StringImports,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Read and parse the source code.
|
// Parse the source code.
|
||||||
let source = std::fs::read_to_string(path)?;
|
let parsed = parse(source, ParseOptions::from(source_type))?;
|
||||||
let parsed = parse(&source, ParseOptions::from(Mode::Module))?;
|
|
||||||
|
|
||||||
let module_path =
|
let module_path =
|
||||||
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));
|
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.14.1"
|
version = "0.14.3"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||||
|
|
||||||
DAG(dag_id="class_timetable", timetable=NullTimetable())
|
DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||||
|
|
||||||
|
DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
|
||||||
DAG(dag_id="class_fail_stop", fail_stop=True)
|
DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from airflow.datasets import (
|
||||||
)
|
)
|
||||||
from airflow.datasets.manager import DatasetManager
|
from airflow.datasets.manager import DatasetManager
|
||||||
from airflow.lineage.hook import DatasetLineageInfo, HookLineageCollector
|
from airflow.lineage.hook import DatasetLineageInfo, HookLineageCollector
|
||||||
|
from airflow.models.dag import DAG
|
||||||
from airflow.providers.amazon.aws.auth_manager.aws_auth_manager import AwsAuthManager
|
from airflow.providers.amazon.aws.auth_manager.aws_auth_manager import AwsAuthManager
|
||||||
from airflow.providers.apache.beam.hooks import BeamHook, NotAir302HookError
|
from airflow.providers.apache.beam.hooks import BeamHook, NotAir302HookError
|
||||||
from airflow.providers.google.cloud.secrets.secret_manager import (
|
from airflow.providers.google.cloud.secrets.secret_manager import (
|
||||||
|
|
@ -20,6 +21,7 @@ from airflow.providers_manager import ProvidersManager
|
||||||
from airflow.secrets.base_secrets import BaseSecretsBackend
|
from airflow.secrets.base_secrets import BaseSecretsBackend
|
||||||
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||||
|
|
||||||
|
|
||||||
# airflow.Dataset
|
# airflow.Dataset
|
||||||
dataset_from_root = DatasetFromRoot()
|
dataset_from_root = DatasetFromRoot()
|
||||||
dataset_from_root.iter_datasets()
|
dataset_from_root.iter_datasets()
|
||||||
|
|
@ -56,6 +58,10 @@ hlc.add_input_dataset()
|
||||||
hlc.add_output_dataset()
|
hlc.add_output_dataset()
|
||||||
hlc.collected_datasets()
|
hlc.collected_datasets()
|
||||||
|
|
||||||
|
# airflow.models.dag.DAG
|
||||||
|
test_dag = DAG(dag_id="test_dag")
|
||||||
|
test_dag.create_dagrun()
|
||||||
|
|
||||||
# airflow.providers.amazon.auth_manager.aws_auth_manager
|
# airflow.providers.amazon.auth_manager.aws_auth_manager
|
||||||
aam = AwsAuthManager()
|
aam = AwsAuthManager()
|
||||||
aam.is_authorized_dataset()
|
aam.is_authorized_dataset()
|
||||||
|
|
@ -96,3 +102,15 @@ base_secret_backend.get_connections()
|
||||||
# airflow.secrets.local_filesystem
|
# airflow.secrets.local_filesystem
|
||||||
lfb = LocalFilesystemBackend()
|
lfb = LocalFilesystemBackend()
|
||||||
lfb.get_connections()
|
lfb.get_connections()
|
||||||
|
|
||||||
|
from airflow.models import DAG
|
||||||
|
|
||||||
|
# airflow.DAG
|
||||||
|
test_dag = DAG(dag_id="test_dag")
|
||||||
|
test_dag.create_dagrun()
|
||||||
|
|
||||||
|
from airflow import DAG
|
||||||
|
|
||||||
|
# airflow.DAG
|
||||||
|
test_dag = DAG(dag_id="test_dag")
|
||||||
|
test_dag.create_dagrun()
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,20 @@ get_unique_task_id()
|
||||||
task_decorator_factory()
|
task_decorator_factory()
|
||||||
|
|
||||||
|
|
||||||
from airflow.models import Param
|
from airflow.models import DagParam, Param, ParamsDict
|
||||||
|
|
||||||
# airflow.models
|
# airflow.models
|
||||||
Param()
|
Param()
|
||||||
|
DagParam()
|
||||||
|
ParamsDict()
|
||||||
|
|
||||||
|
|
||||||
|
from airflow.models.param import DagParam, Param, ParamsDict
|
||||||
|
|
||||||
|
# airflow.models.param
|
||||||
|
Param()
|
||||||
|
DagParam()
|
||||||
|
ParamsDict()
|
||||||
|
|
||||||
|
|
||||||
from airflow.sensors.base import (
|
from airflow.sensors.base import (
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,12 @@ builtins.getattr(foo, "bar")
|
||||||
|
|
||||||
# Regression test for: https://github.com/astral-sh/ruff/issues/18353
|
# Regression test for: https://github.com/astral-sh/ruff/issues/18353
|
||||||
setattr(foo, "__debug__", 0)
|
setattr(foo, "__debug__", 0)
|
||||||
|
|
||||||
|
# Regression test for: https://github.com/astral-sh/ruff/issues/21126
|
||||||
|
# Non-NFKC attribute names should be marked as unsafe. Python normalizes identifiers in
|
||||||
|
# attribute access (obj.attr) using NFKC, but does not normalize string
|
||||||
|
# arguments passed to getattr/setattr. Rewriting `getattr(ns, "ſ")` to
|
||||||
|
# `ns.ſ` would be interpreted as `ns.s` at runtime, changing behavior.
|
||||||
|
# Example: the long s character "ſ" normalizes to "s" under NFKC.
|
||||||
|
getattr(foo, "ſ")
|
||||||
|
setattr(foo, "ſ", 1)
|
||||||
|
|
|
||||||
|
|
@ -46,3 +46,9 @@ class CorrectModel(models.Model):
|
||||||
max_length=255, null=True, blank=True, unique=True
|
max_length=255, null=True, blank=True, unique=True
|
||||||
)
|
)
|
||||||
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)
|
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectModelWithSimpleAnnotations(models.Model):
|
||||||
|
charfield: models.CharField = models.CharField(max_length=255, null=True)
|
||||||
|
textfield: models.TextField = models.TextField(max_length=255, null=True)
|
||||||
|
slugfield: models.SlugField = models.SlugField(max_length=255, null=True)
|
||||||
|
|
|
||||||
7
crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC_syntax_error_2.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC_syntax_error_2.py
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Regression test for https://github.com/astral-sh/ruff/issues/21023
|
||||||
|
'' '
|
||||||
|
"" ""
|
||||||
|
'' '' '
|
||||||
|
"" "" "
|
||||||
|
f"" f"
|
||||||
|
f"" f"" f"
|
||||||
|
|
@ -359,3 +359,29 @@ class Generic5(list[PotentialTypeVar]):
|
||||||
def __new__(cls: type[Generic5]) -> Generic5: ...
|
def __new__(cls: type[Generic5]) -> Generic5: ...
|
||||||
def __enter__(self: Generic5) -> Generic5: ...
|
def __enter__(self: Generic5) -> Generic5: ...
|
||||||
|
|
||||||
|
|
||||||
|
# Test cases based on issue #20781 - metaclasses that triggers IsMetaclass::Maybe
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
|
||||||
|
def __new__(
|
||||||
|
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
|
||||||
|
) -> MetaclassInWhichSelfCannotBeUsed5:
|
||||||
|
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
||||||
|
return new_class
|
||||||
|
|
||||||
|
|
||||||
|
import django.db.models.base
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
|
||||||
|
...
|
||||||
|
|
|
||||||
|
|
@ -252,3 +252,28 @@ from some_module import PotentialTypeVar
|
||||||
class Generic5(list[PotentialTypeVar]):
|
class Generic5(list[PotentialTypeVar]):
|
||||||
def __new__(cls: type[Generic5]) -> Generic5: ...
|
def __new__(cls: type[Generic5]) -> Generic5: ...
|
||||||
def __enter__(self: Generic5) -> Generic5: ...
|
def __enter__(self: Generic5) -> Generic5: ...
|
||||||
|
|
||||||
|
|
||||||
|
# Test case based on issue #20781 - metaclass that triggers IsMetaclass::Maybe
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
|
||||||
|
def __new__(
|
||||||
|
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
|
||||||
|
) -> MetaclassInWhichSelfCannotBeUsed5: ...
|
||||||
|
|
||||||
|
|
||||||
|
import django.db.models.base
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
|
||||||
|
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
|
||||||
|
...
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,7 @@ def foo():
|
||||||
# https://github.com/astral-sh/ruff/issues/18776
|
# https://github.com/astral-sh/ruff/issues/18776
|
||||||
flag_stars = {}
|
flag_stars = {}
|
||||||
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...
|
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...
|
||||||
|
|
||||||
|
# Regression test for https://github.com/astral-sh/ruff/issues/18778
|
||||||
|
d = {}
|
||||||
|
for country, stars in zip(d.keys(*x), d.values("hello")):...
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,14 @@ def f():
|
||||||
import os
|
import os
|
||||||
|
|
||||||
print(os)
|
print(os)
|
||||||
|
|
||||||
|
|
||||||
|
# regression test for https://github.com/astral-sh/ruff/issues/21121
|
||||||
|
from dataclasses import KW_ONLY, dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataClass:
|
||||||
|
a: int
|
||||||
|
_: KW_ONLY # should be an exception to TC003, even with future-annotations
|
||||||
|
b: int
|
||||||
|
|
|
||||||
|
|
@ -370,3 +370,22 @@ class Foo:
|
||||||
The flag converter instance with all flags parsed.
|
The flag converter instance with all flags parsed.
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# OK
|
||||||
|
def baz(x: int) -> int:
|
||||||
|
"""
|
||||||
|
Show a `Warnings` DOC102 false positive.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
x : int
|
||||||
|
|
||||||
|
Warnings
|
||||||
|
--------
|
||||||
|
This function demonstrates a DOC102 false positive
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
"""
|
||||||
|
return x
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,55 @@ def calculate_speed(distance: float, time: float) -> float:
|
||||||
except TypeError:
|
except TypeError:
|
||||||
print("Not a number? Shame on you!")
|
print("Not a number? Shame on you!")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
# This should NOT trigger DOC502 because OSError is explicitly re-raised
|
||||||
|
def f():
|
||||||
|
"""Do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: If the OS errors.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except OSError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
# This should NOT trigger DOC502 because OSError is explicitly re-raised with from None
|
||||||
|
def g():
|
||||||
|
"""Do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: If the OS errors.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except OSError as e:
|
||||||
|
raise e from None
|
||||||
|
|
||||||
|
|
||||||
|
# This should NOT trigger DOC502 because ValueError is explicitly re-raised from tuple exception
|
||||||
|
def h():
|
||||||
|
"""Do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If something goes wrong.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
# This should NOT trigger DOC502 because TypeError is explicitly re-raised from tuple exception
|
||||||
|
def i():
|
||||||
|
"""Do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If something goes wrong.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise e
|
||||||
|
|
|
||||||
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_33.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_33.py
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
class C:
|
||||||
|
f = lambda self: __class__
|
||||||
|
|
||||||
|
|
||||||
|
print(C().f().__name__)
|
||||||
|
|
||||||
|
# Test: nested lambda
|
||||||
|
class D:
|
||||||
|
g = lambda self: (lambda: __class__)
|
||||||
|
|
||||||
|
|
||||||
|
print(D().g()().__name__)
|
||||||
|
|
||||||
|
# Test: lambda outside class (should still fail)
|
||||||
|
h = lambda: __class__
|
||||||
|
|
||||||
|
# Test: lambda referencing module-level variable (should not be flagged as F821)
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class E:
|
||||||
|
uuid = lambda: str(uuid.uuid4())
|
||||||
131
crates/ruff_linter/resources/test/fixtures/pylint/stop_iteration_return.py
vendored
Normal file
131
crates/ruff_linter/resources/test/fixtures/pylint/stop_iteration_return.py
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
"""Test cases for PLR1708 stop-iteration-return."""
|
||||||
|
|
||||||
|
|
||||||
|
# Valid cases - should not trigger the rule
|
||||||
|
def normal_function():
|
||||||
|
raise StopIteration # Not a generator, should not trigger
|
||||||
|
|
||||||
|
|
||||||
|
def normal_function_with_value():
|
||||||
|
raise StopIteration("value") # Not a generator, should not trigger
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_return():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
return "finished" # This is the correct way
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_yield_from():
|
||||||
|
yield from [1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def generator_without_stop_iteration():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
# No explicit termination
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_other_exception():
|
||||||
|
yield 1
|
||||||
|
raise ValueError("something else") # Different exception
|
||||||
|
|
||||||
|
|
||||||
|
# Invalid cases - should trigger the rule
|
||||||
|
def generator_with_stop_iteration():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
raise StopIteration # Should trigger
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_stop_iteration_value():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
raise StopIteration("finished") # Should trigger
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_stop_iteration_expr():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
raise StopIteration(1 + 2) # Should trigger
|
||||||
|
|
||||||
|
|
||||||
|
def async_generator_with_stop_iteration():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
raise StopIteration("async") # Should trigger
|
||||||
|
|
||||||
|
|
||||||
|
def nested_generator():
|
||||||
|
def inner_gen():
|
||||||
|
yield 1
|
||||||
|
raise StopIteration("inner") # Should trigger
|
||||||
|
|
||||||
|
yield from inner_gen()
|
||||||
|
|
||||||
|
|
||||||
|
def generator_in_class():
|
||||||
|
class MyClass:
|
||||||
|
def generator_method(self):
|
||||||
|
yield 1
|
||||||
|
raise StopIteration("method") # Should trigger
|
||||||
|
|
||||||
|
return MyClass
|
||||||
|
|
||||||
|
|
||||||
|
# Complex cases
|
||||||
|
def complex_generator():
|
||||||
|
try:
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
raise StopIteration("complex") # Should trigger
|
||||||
|
except ValueError:
|
||||||
|
yield 3
|
||||||
|
finally:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_conditional_stop_iteration(condition):
|
||||||
|
yield 1
|
||||||
|
if condition:
|
||||||
|
raise StopIteration("conditional") # Should trigger
|
||||||
|
yield 2
|
||||||
|
|
||||||
|
|
||||||
|
# Edge cases
|
||||||
|
def generator_with_bare_stop_iteration():
|
||||||
|
yield 1
|
||||||
|
raise StopIteration # Should trigger (no arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_stop_iteration_in_loop():
|
||||||
|
for i in range(5):
|
||||||
|
yield i
|
||||||
|
if i == 3:
|
||||||
|
raise StopIteration("loop") # Should trigger
|
||||||
|
|
||||||
|
|
||||||
|
# Should not trigger - different exceptions
|
||||||
|
def generator_with_runtime_error():
|
||||||
|
yield 1
|
||||||
|
raise RuntimeError("not StopIteration") # Should not trigger
|
||||||
|
|
||||||
|
|
||||||
|
def generator_with_custom_exception():
|
||||||
|
yield 1
|
||||||
|
raise CustomException("custom") # Should not trigger
|
||||||
|
|
||||||
|
|
||||||
|
class CustomException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Generator comprehensions should not be affected
|
||||||
|
list_comp = [x for x in range(10)] # Should not trigger
|
||||||
|
|
||||||
|
|
||||||
|
# Lambda in generator context
|
||||||
|
def generator_with_lambda():
|
||||||
|
yield 1
|
||||||
|
func = lambda x: x # Just a regular lambda
|
||||||
|
yield 2
|
||||||
|
|
@ -26,3 +26,8 @@
|
||||||
"{.real}".format({1, 2})
|
"{.real}".format({1, 2})
|
||||||
"{.real}".format({1: 2, 3: 4})
|
"{.real}".format({1: 2, 3: 4})
|
||||||
"{}".format((i for i in range(2)))
|
"{}".format((i for i in range(2)))
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/21017
|
||||||
|
"{.real}".format(1_2)
|
||||||
|
"{0.real}".format(1_2)
|
||||||
|
"{a.real}".format(a=1_2)
|
||||||
|
|
|
||||||
17
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_3.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_3.py
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
"""
|
||||||
|
Regression test for an ecosystem hit on
|
||||||
|
https://github.com/astral-sh/ruff/pull/21125.
|
||||||
|
|
||||||
|
We should mark all of the components of special dataclass annotations as
|
||||||
|
runtime-required, not just the first layer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import ClassVar, Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class EmptyCell:
|
||||||
|
_singleton: ClassVar[Optional["EmptyCell"]] = None
|
||||||
|
# the behavior of _singleton above should match a non-ClassVar
|
||||||
|
_doubleton: "EmptyCell"
|
||||||
13
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_2.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_2.py
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""This is placed in a separate fixture as `TypeVar` needs to be imported
|
||||||
|
from `typing_extensions` to support default arguments in Python version < 3.13.
|
||||||
|
We verify that UP046 doesn't apply in this case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Generic
|
||||||
|
from typing_extensions import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T", default=str)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultTypeVar(Generic[T]):
|
||||||
|
var: T
|
||||||
12
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047_1.py
vendored
Normal file
12
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047_1.py
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""This is placed in a separate fixture as `TypeVar` needs to be imported
|
||||||
|
from `typing_extensions` to support default arguments in Python version < 3.13.
|
||||||
|
We verify that UP047 doesn't apply in this case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing_extensions import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T", default=int)
|
||||||
|
|
||||||
|
|
||||||
|
def default_var(var: T) -> T:
|
||||||
|
return var
|
||||||
|
|
@ -145,3 +145,11 @@ with open("file.txt", "w") as f:
|
||||||
with open("file.txt", "w") as f:
|
with open("file.txt", "w") as f:
|
||||||
for line in text:
|
for line in text:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
|
|
||||||
|
# See: https://github.com/astral-sh/ruff/issues/20785
|
||||||
|
import json
|
||||||
|
|
||||||
|
data = {"price": 100}
|
||||||
|
|
||||||
|
with open("test.json", "wb") as f:
|
||||||
|
f.write(json.dumps(data, indent=4).encode("utf-8"))
|
||||||
|
|
@ -69,3 +69,25 @@ Decimal(float("\N{space}\N{hyPHen-MINus}nan"))
|
||||||
Decimal(float("\x20\N{character tabulation}\N{hyphen-minus}nan"))
|
Decimal(float("\x20\N{character tabulation}\N{hyphen-minus}nan"))
|
||||||
Decimal(float(" -" "nan"))
|
Decimal(float(" -" "nan"))
|
||||||
Decimal(float("-nAn"))
|
Decimal(float("-nAn"))
|
||||||
|
|
||||||
|
# Test cases for digit separators (safe fixes)
|
||||||
|
# https://github.com/astral-sh/ruff/issues/20572
|
||||||
|
Decimal("15_000_000") # Safe fix: normalizes separators, becomes Decimal(15_000_000)
|
||||||
|
Decimal("1_234_567") # Safe fix: normalizes separators, becomes Decimal(1_234_567)
|
||||||
|
Decimal("-5_000") # Safe fix: normalizes separators, becomes Decimal(-5_000)
|
||||||
|
Decimal("+9_999") # Safe fix: normalizes separators, becomes Decimal(+9_999)
|
||||||
|
|
||||||
|
# Test cases for non-thousands separators
|
||||||
|
Decimal("12_34_56_78") # Safe fix: preserves non-thousands separators
|
||||||
|
Decimal("1234_5678") # Safe fix: preserves non-thousands separators
|
||||||
|
|
||||||
|
# Separators _and_ leading zeros
|
||||||
|
Decimal("0001_2345")
|
||||||
|
Decimal("000_1_2345")
|
||||||
|
Decimal("000_000")
|
||||||
|
|
||||||
|
# Test cases for underscores before sign
|
||||||
|
# https://github.com/astral-sh/ruff/issues/21186
|
||||||
|
Decimal("_-1") # Should flag as verbose
|
||||||
|
Decimal("_+1") # Should flag as verbose
|
||||||
|
Decimal("_-1_000") # Should flag as verbose
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,8 @@ _ = Decimal.from_float(True)
|
||||||
_ = Decimal.from_float(float("-nan"))
|
_ = Decimal.from_float(float("-nan"))
|
||||||
_ = Decimal.from_float(float("\x2dnan"))
|
_ = Decimal.from_float(float("\x2dnan"))
|
||||||
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||||
|
|
||||||
|
# See: https://github.com/astral-sh/ruff/issues/21257
|
||||||
|
# fixes must be safe
|
||||||
|
_ = Fraction.from_float(f=4.2)
|
||||||
|
_ = Fraction.from_decimal(dec=4)
|
||||||
|
|
@ -81,3 +81,7 @@ round(# a comment
|
||||||
round(
|
round(
|
||||||
17 # a comment
|
17 # a comment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# See: https://github.com/astral-sh/ruff/issues/21209
|
||||||
|
print(round(125, **{"ndigits": -2}))
|
||||||
|
print(round(125, *[-2]))
|
||||||
|
|
@ -43,3 +43,29 @@ logging.warning("Value: %r", repr(42))
|
||||||
logging.error("Error: %r", repr([1, 2, 3]))
|
logging.error("Error: %r", repr([1, 2, 3]))
|
||||||
logging.info("Debug info: %s", repr("test\nstring"))
|
logging.info("Debug info: %s", repr("test\nstring"))
|
||||||
logging.warning("Value: %s", repr(42))
|
logging.warning("Value: %s", repr(42))
|
||||||
|
|
||||||
|
# %s + ascii()
|
||||||
|
logging.info("ASCII: %s", ascii("Hello\nWorld"))
|
||||||
|
logging.warning("ASCII: %s", ascii("test"))
|
||||||
|
|
||||||
|
# %s + oct()
|
||||||
|
logging.info("Octal: %s", oct(42))
|
||||||
|
logging.warning("Octal: %s", oct(255))
|
||||||
|
|
||||||
|
# %s + hex()
|
||||||
|
logging.info("Hex: %s", hex(42))
|
||||||
|
logging.warning("Hex: %s", hex(255))
|
||||||
|
|
||||||
|
|
||||||
|
# Test with imported functions
|
||||||
|
from logging import info, log
|
||||||
|
|
||||||
|
info("ASCII: %s", ascii("Hello\nWorld"))
|
||||||
|
log(logging.INFO, "ASCII: %s", ascii("test"))
|
||||||
|
|
||||||
|
info("Octal: %s", oct(42))
|
||||||
|
log(logging.INFO, "Octal: %s", oct(255))
|
||||||
|
|
||||||
|
info("Hex: %s", hex(42))
|
||||||
|
log(logging.INFO, "Hex: %s", hex(255))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
async def f(): return [[x async for x in foo(n)] for n in range(3)]
|
||||||
|
|
||||||
|
async def test(): return [[x async for x in elements(n)] async for n in range(3)]
|
||||||
|
|
||||||
|
async def f(): [x for x in foo()] and [x async for x in foo()]
|
||||||
|
|
||||||
|
async def f():
|
||||||
|
def g(): ...
|
||||||
|
[x async for x in foo()]
|
||||||
|
|
||||||
|
[x async for x in y]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
match x:
|
||||||
|
case Point(x=1, x=2):
|
||||||
|
pass
|
||||||
3
crates/ruff_linter/resources/test/fixtures/semantic_errors/duplicate_match_key.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/semantic_errors/duplicate_match_key.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
match x:
|
||||||
|
case {'key': 1, 'key': 2}:
|
||||||
|
pass
|
||||||
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/duplicate_type_parameter.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/duplicate_type_parameter.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
class C[T, T]: pass
|
||||||
29
crates/ruff_linter/resources/test/fixtures/semantic_errors/global_parameter.py
vendored
Normal file
29
crates/ruff_linter/resources/test/fixtures/semantic_errors/global_parameter.py
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
def f(a):
|
||||||
|
global a
|
||||||
|
|
||||||
|
def g(a):
|
||||||
|
if True:
|
||||||
|
global a
|
||||||
|
|
||||||
|
def h(a):
|
||||||
|
def inner():
|
||||||
|
global a
|
||||||
|
|
||||||
|
def i(a):
|
||||||
|
try:
|
||||||
|
global a
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def f(a):
|
||||||
|
a = 1
|
||||||
|
global a
|
||||||
|
|
||||||
|
def f(a):
|
||||||
|
a = 1
|
||||||
|
a = 2
|
||||||
|
global a
|
||||||
|
|
||||||
|
def f(a):
|
||||||
|
class Inner:
|
||||||
|
global a # ok
|
||||||
8
crates/ruff_linter/resources/test/fixtures/semantic_errors/invalid_expression.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/semantic_errors/invalid_expression.py
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
type X[T: (yield 1)] = int
|
||||||
|
|
||||||
|
type Y = (yield 1)
|
||||||
|
|
||||||
|
def f[T](x: int) -> (y := 3): return x
|
||||||
|
|
||||||
|
class C[T]((yield from [object])):
|
||||||
|
pass
|
||||||
8
crates/ruff_linter/resources/test/fixtures/semantic_errors/invalid_star_expression.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/semantic_errors/invalid_star_expression.py
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
def func():
|
||||||
|
return *x
|
||||||
|
|
||||||
|
for *x in range(10):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def func():
|
||||||
|
yield *x
|
||||||
11
crates/ruff_linter/resources/test/fixtures/semantic_errors/irrefutable_case_pattern.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/semantic_errors/irrefutable_case_pattern.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
match value:
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
case 1:
|
||||||
|
pass
|
||||||
|
|
||||||
|
match value:
|
||||||
|
case irrefutable:
|
||||||
|
pass
|
||||||
|
case 1:
|
||||||
|
pass
|
||||||
5
crates/ruff_linter/resources/test/fixtures/semantic_errors/multiple_case_assignment.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/semantic_errors/multiple_case_assignment.py
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
match x:
|
||||||
|
case [a, a]:
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/rebound_comprehension.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/rebound_comprehension.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[x:= 2 for x in range(2)]
|
||||||
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/single_starred_assignment.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/semantic_errors/single_starred_assignment.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*a = [1, 2, 3, 4]
|
||||||
7
crates/ruff_linter/resources/test/fixtures/semantic_errors/write_to_debug.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/semantic_errors/write_to_debug.py
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
__debug__ = False
|
||||||
|
|
||||||
|
def process(__debug__):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Generic[__debug__]:
|
||||||
|
pass
|
||||||
|
|
@ -43,9 +43,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
pycodestyle::rules::ambiguous_variable_name(checker, name, name.range());
|
pycodestyle::rules::ambiguous_variable_name(checker, name, name.range());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.is_rule_enabled(Rule::NonlocalWithoutBinding) {
|
|
||||||
pylint::rules::nonlocal_without_binding(checker, nonlocal);
|
|
||||||
}
|
|
||||||
if checker.is_rule_enabled(Rule::NonlocalAndGlobal) {
|
if checker.is_rule_enabled(Rule::NonlocalAndGlobal) {
|
||||||
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
||||||
}
|
}
|
||||||
|
|
@ -951,6 +948,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.is_rule_enabled(Rule::MisplacedBareRaise) {
|
if checker.is_rule_enabled(Rule::MisplacedBareRaise) {
|
||||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||||
}
|
}
|
||||||
|
if checker.is_rule_enabled(Rule::StopIterationReturn) {
|
||||||
|
pylint::rules::stop_iteration_return(checker, raise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
||||||
if checker.is_rule_enabled(Rule::GlobalStatement) {
|
if checker.is_rule_enabled(Rule::GlobalStatement) {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ use crate::rules::pyflakes::rules::{
|
||||||
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
||||||
};
|
};
|
||||||
use crate::rules::pylint::rules::{
|
use crate::rules::pylint::rules::{
|
||||||
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, YieldFromInAsyncFunction,
|
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, NonlocalWithoutBinding,
|
||||||
|
YieldFromInAsyncFunction,
|
||||||
};
|
};
|
||||||
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
|
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
|
||||||
use crate::settings::rule_table::RuleTable;
|
use crate::settings::rule_table::RuleTable;
|
||||||
|
|
@ -641,6 +642,10 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
self.semantic.global(name)
|
self.semantic.global(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_nonlocal_binding(&self, name: &str) -> bool {
|
||||||
|
self.semantic.nonlocal(name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn report_semantic_error(&self, error: SemanticSyntaxError) {
|
fn report_semantic_error(&self, error: SemanticSyntaxError) {
|
||||||
match error.kind {
|
match error.kind {
|
||||||
SemanticSyntaxErrorKind::LateFutureImport => {
|
SemanticSyntaxErrorKind::LateFutureImport => {
|
||||||
|
|
@ -717,6 +722,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
self.report_diagnostic(pyflakes::rules::ContinueOutsideLoop, error.range);
|
self.report_diagnostic(pyflakes::rules::ContinueOutsideLoop, error.range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SemanticSyntaxErrorKind::NonlocalWithoutBinding(name) => {
|
||||||
|
// PLE0117
|
||||||
|
if self.is_rule_enabled(Rule::NonlocalWithoutBinding) {
|
||||||
|
self.report_diagnostic(NonlocalWithoutBinding { name }, error.range);
|
||||||
|
}
|
||||||
|
}
|
||||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||||
|
|
@ -725,6 +736,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
| SemanticSyntaxErrorKind::WriteToDebug(_)
|
| SemanticSyntaxErrorKind::WriteToDebug(_)
|
||||||
| SemanticSyntaxErrorKind::DifferentMatchPatternBindings
|
| SemanticSyntaxErrorKind::DifferentMatchPatternBindings
|
||||||
| SemanticSyntaxErrorKind::InvalidExpression(..)
|
| SemanticSyntaxErrorKind::InvalidExpression(..)
|
||||||
|
| SemanticSyntaxErrorKind::GlobalParameter(_)
|
||||||
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
||||||
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
||||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||||
|
|
@ -846,6 +858,26 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_bound_parameter(&self, name: &str) -> bool {
|
||||||
|
for scope in self.semantic.current_scopes() {
|
||||||
|
match scope.kind {
|
||||||
|
ScopeKind::Class(_) => return false,
|
||||||
|
ScopeKind::Function(ast::StmtFunctionDef { parameters, .. })
|
||||||
|
| ScopeKind::Lambda(ast::ExprLambda {
|
||||||
|
parameters: Some(parameters),
|
||||||
|
..
|
||||||
|
}) => return parameters.includes(name),
|
||||||
|
ScopeKind::Lambda(_)
|
||||||
|
| ScopeKind::Generator { .. }
|
||||||
|
| ScopeKind::Module
|
||||||
|
| ScopeKind::Type
|
||||||
|
| ScopeKind::DunderClassCell => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for Checker<'a> {
|
impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
|
|
@ -1379,6 +1411,14 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
AnnotationContext::RuntimeRequired => {
|
AnnotationContext::RuntimeRequired => {
|
||||||
self.visit_runtime_required_annotation(annotation);
|
self.visit_runtime_required_annotation(annotation);
|
||||||
}
|
}
|
||||||
|
AnnotationContext::RuntimeEvaluated
|
||||||
|
if flake8_type_checking::helpers::is_dataclass_meta_annotation(
|
||||||
|
annotation,
|
||||||
|
self.semantic(),
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
self.visit_runtime_required_annotation(annotation);
|
||||||
|
}
|
||||||
AnnotationContext::RuntimeEvaluated => {
|
AnnotationContext::RuntimeEvaluated => {
|
||||||
self.visit_runtime_evaluated_annotation(annotation);
|
self.visit_runtime_evaluated_annotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
@ -2095,7 +2135,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
| Expr::DictComp(_)
|
| Expr::DictComp(_)
|
||||||
| Expr::SetComp(_) => {
|
| Expr::SetComp(_) => {
|
||||||
self.analyze.scopes.push(self.semantic.scope_id);
|
self.analyze.scopes.push(self.semantic.scope_id);
|
||||||
self.semantic.pop_scope();
|
self.semantic.pop_scope(); // Lambda/Generator/Comprehension scope
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -3020,7 +3060,35 @@ impl<'a> Checker<'a> {
|
||||||
if let Some(parameters) = parameters {
|
if let Some(parameters) = parameters {
|
||||||
self.visit_parameters(parameters);
|
self.visit_parameters(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we add the implicit scope surrounding a lambda which allows code in the
|
||||||
|
// lambda to access `__class__` at runtime when the lambda is defined within a class.
|
||||||
|
// See the `ScopeKind::DunderClassCell` docs for more information.
|
||||||
|
let added_dunder_class_scope = if self
|
||||||
|
.semantic
|
||||||
|
.current_scopes()
|
||||||
|
.any(|scope| scope.kind.is_class())
|
||||||
|
{
|
||||||
|
self.semantic.push_scope(ScopeKind::DunderClassCell);
|
||||||
|
let binding_id = self.semantic.push_binding(
|
||||||
|
TextRange::default(),
|
||||||
|
BindingKind::DunderClassCell,
|
||||||
|
BindingFlags::empty(),
|
||||||
|
);
|
||||||
|
self.semantic
|
||||||
|
.current_scope_mut()
|
||||||
|
.add("__class__", binding_id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
self.visit_expr(body);
|
self.visit_expr(body);
|
||||||
|
|
||||||
|
// Pop the DunderClassCell scope if it was added
|
||||||
|
if added_dunder_class_scope {
|
||||||
|
self.semantic.pop_scope();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.semantic.restore(snapshot);
|
self.semantic.restore(snapshot);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,8 @@ pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
|
||||||
SectionKind::References,
|
SectionKind::References,
|
||||||
SectionKind::Returns,
|
SectionKind::Returns,
|
||||||
SectionKind::SeeAlso,
|
SectionKind::SeeAlso,
|
||||||
|
SectionKind::Warnings,
|
||||||
|
SectionKind::Warns,
|
||||||
SectionKind::Yields,
|
SectionKind::Yields,
|
||||||
// Google-only
|
// Google-only
|
||||||
SectionKind::Args,
|
SectionKind::Args,
|
||||||
|
|
@ -32,7 +34,5 @@ pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
|
||||||
SectionKind::Tip,
|
SectionKind::Tip,
|
||||||
SectionKind::Todo,
|
SectionKind::Todo,
|
||||||
SectionKind::Warning,
|
SectionKind::Warning,
|
||||||
SectionKind::Warnings,
|
|
||||||
SectionKind::Warns,
|
|
||||||
SectionKind::Yield,
|
SectionKind::Yield,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@ pub(crate) static NUMPY_SECTIONS: &[SectionKind] = &[
|
||||||
SectionKind::References,
|
SectionKind::References,
|
||||||
SectionKind::Returns,
|
SectionKind::Returns,
|
||||||
SectionKind::SeeAlso,
|
SectionKind::SeeAlso,
|
||||||
|
SectionKind::Warnings,
|
||||||
|
SectionKind::Warns,
|
||||||
SectionKind::Yields,
|
SectionKind::Yields,
|
||||||
// NumPy-only
|
// NumPy-only
|
||||||
SectionKind::ExtendedSummary,
|
SectionKind::ExtendedSummary,
|
||||||
SectionKind::OtherParams,
|
SectionKind::OtherParams,
|
||||||
SectionKind::OtherParameters,
|
SectionKind::OtherParameters,
|
||||||
SectionKind::Parameters,
|
SectionKind::Parameters,
|
||||||
|
SectionKind::Receives,
|
||||||
SectionKind::ShortSummary,
|
SectionKind::ShortSummary,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ pub(crate) enum SectionKind {
|
||||||
OtherParameters,
|
OtherParameters,
|
||||||
Parameters,
|
Parameters,
|
||||||
Raises,
|
Raises,
|
||||||
|
Receives,
|
||||||
References,
|
References,
|
||||||
Return,
|
Return,
|
||||||
Returns,
|
Returns,
|
||||||
|
|
@ -76,6 +77,7 @@ impl SectionKind {
|
||||||
"other parameters" => Some(Self::OtherParameters),
|
"other parameters" => Some(Self::OtherParameters),
|
||||||
"parameters" => Some(Self::Parameters),
|
"parameters" => Some(Self::Parameters),
|
||||||
"raises" => Some(Self::Raises),
|
"raises" => Some(Self::Raises),
|
||||||
|
"receives" => Some(Self::Receives),
|
||||||
"references" => Some(Self::References),
|
"references" => Some(Self::References),
|
||||||
"return" => Some(Self::Return),
|
"return" => Some(Self::Return),
|
||||||
"returns" => Some(Self::Returns),
|
"returns" => Some(Self::Returns),
|
||||||
|
|
@ -117,6 +119,7 @@ impl SectionKind {
|
||||||
Self::OtherParameters => "Other Parameters",
|
Self::OtherParameters => "Other Parameters",
|
||||||
Self::Parameters => "Parameters",
|
Self::Parameters => "Parameters",
|
||||||
Self::Raises => "Raises",
|
Self::Raises => "Raises",
|
||||||
|
Self::Receives => "Receives",
|
||||||
Self::References => "References",
|
Self::References => "References",
|
||||||
Self::Return => "Return",
|
Self::Return => "Return",
|
||||||
Self::Returns => "Returns",
|
Self::Returns => "Returns",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use ruff_text_size::TextSize;
|
||||||
/// The length of a line of text that is considered too long.
|
/// The length of a line of text that is considered too long.
|
||||||
///
|
///
|
||||||
/// The allowed range of values is 1..=320
|
/// The allowed range of values is 1..=320
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct LineLength(
|
pub struct LineLength(
|
||||||
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16,
|
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16,
|
||||||
|
|
@ -46,6 +46,21 @@ impl fmt::Display for LineLength {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for LineLength {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = u16::deserialize(deserializer)?;
|
||||||
|
Self::try_from(value).map_err(|_| {
|
||||||
|
serde::de::Error::custom(format!(
|
||||||
|
"line-length must be between 1 and {} (got {value})",
|
||||||
|
Self::MAX,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CacheKey for LineLength {
|
impl CacheKey for LineLength {
|
||||||
fn cache_key(&self, state: &mut CacheKeyHasher) {
|
fn cache_key(&self, state: &mut CacheKeyHasher) {
|
||||||
state.write_u16(self.0.get());
|
state.write_u16(self.0.get());
|
||||||
|
|
|
||||||
|
|
@ -919,17 +919,6 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
|
|
||||||
/// file.
|
|
||||||
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Diagnostic> {
|
|
||||||
let contents = dedent(contents);
|
|
||||||
test_contents_syntax_errors(
|
|
||||||
&SourceKind::Python(contents.to_string()),
|
|
||||||
Path::new("<filename>"),
|
|
||||||
settings,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A custom test runner that prints syntax errors in addition to other diagnostics. Adapted
|
/// A custom test runner that prints syntax errors in addition to other diagnostics. Adapted
|
||||||
/// from `flakes` in pyflakes/mod.rs.
|
/// from `flakes` in pyflakes/mod.rs.
|
||||||
fn test_contents_syntax_errors(
|
fn test_contents_syntax_errors(
|
||||||
|
|
@ -972,209 +961,38 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"async_in_sync_error_on_310",
|
Path::new("async_comprehension_outside_async_function.py"),
|
||||||
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
PythonVersion::PY311
|
||||||
PythonVersion::PY310,
|
|
||||||
"AsyncComprehensionOutsideAsyncFunction"
|
|
||||||
)]
|
)]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"async_in_sync_okay_on_311",
|
Path::new("async_comprehension_outside_async_function.py"),
|
||||||
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
PythonVersion::PY310
|
||||||
PythonVersion::PY311,
|
|
||||||
"AsyncComprehensionOutsideAsyncFunction"
|
|
||||||
)]
|
)]
|
||||||
#[test_case(
|
#[test_case(Path::new("rebound_comprehension.py"), PythonVersion::PY310)]
|
||||||
"async_in_sync_okay_on_310",
|
#[test_case(Path::new("duplicate_type_parameter.py"), PythonVersion::PY312)]
|
||||||
"async def test(): return [[x async for x in elements(n)] async for n in range(3)]",
|
#[test_case(Path::new("multiple_case_assignment.py"), PythonVersion::PY310)]
|
||||||
PythonVersion::PY310,
|
#[test_case(Path::new("duplicate_match_key.py"), PythonVersion::PY310)]
|
||||||
"AsyncComprehensionOutsideAsyncFunction"
|
#[test_case(Path::new("duplicate_match_class_attribute.py"), PythonVersion::PY310)]
|
||||||
)]
|
#[test_case(Path::new("invalid_star_expression.py"), PythonVersion::PY310)]
|
||||||
#[test_case(
|
#[test_case(Path::new("irrefutable_case_pattern.py"), PythonVersion::PY310)]
|
||||||
"deferred_function_body",
|
#[test_case(Path::new("single_starred_assignment.py"), PythonVersion::PY310)]
|
||||||
"
|
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY312)]
|
||||||
async def f(): [x for x in foo()] and [x async for x in foo()]
|
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
||||||
async def f():
|
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
||||||
def g(): ...
|
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
||||||
[x async for x in foo()]
|
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
||||||
",
|
let snapshot = format!(
|
||||||
PythonVersion::PY310,
|
"semantic_syntax_error_{}_{}",
|
||||||
"AsyncComprehensionOutsideAsyncFunction"
|
path.to_string_lossy(),
|
||||||
)]
|
python_version
|
||||||
#[test_case(
|
);
|
||||||
"async_in_sync_false_positive",
|
let path = Path::new("resources/test/fixtures/semantic_errors").join(path);
|
||||||
"[x async for x in y]",
|
let contents = std::fs::read_to_string(&path)?;
|
||||||
PythonVersion::PY310,
|
let source_kind = SourceKind::Python(contents);
|
||||||
"AsyncComprehensionOutsideAsyncFunction"
|
|
||||||
)]
|
let diagnostics = test_contents_syntax_errors(
|
||||||
#[test_case(
|
&source_kind,
|
||||||
"rebound_comprehension",
|
&path,
|
||||||
"[x:= 2 for x in range(2)]",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"ReboundComprehensionVariable"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"duplicate_type_param",
|
|
||||||
"class C[T, T]: pass",
|
|
||||||
PythonVersion::PY312,
|
|
||||||
"DuplicateTypeParameter"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"multiple_case_assignment",
|
|
||||||
"
|
|
||||||
match x:
|
|
||||||
case [a, a]:
|
|
||||||
pass
|
|
||||||
case _:
|
|
||||||
pass
|
|
||||||
",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"MultipleCaseAssignment"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"duplicate_match_key",
|
|
||||||
"
|
|
||||||
match x:
|
|
||||||
case {'key': 1, 'key': 2}:
|
|
||||||
pass
|
|
||||||
",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"DuplicateMatchKey"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"duplicate_match_class_attribute",
|
|
||||||
"
|
|
||||||
match x:
|
|
||||||
case Point(x=1, x=2):
|
|
||||||
pass
|
|
||||||
",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"DuplicateMatchClassAttribute"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"invalid_star_expression",
|
|
||||||
"
|
|
||||||
def func():
|
|
||||||
return *x
|
|
||||||
",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"InvalidStarExpression"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"invalid_star_expression_for",
|
|
||||||
"
|
|
||||||
for *x in range(10):
|
|
||||||
pass
|
|
||||||
",
|
|
||||||
PythonVersion::PY310,
|
|
||||||
"InvalidStarExpression"
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
"invalid_star_expression_yield",
|
|
||||||
"
|
|
||||||
def func():
|
|
||||||
yield *x
|
|
||||||
",
|
|
||||||
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,
|
|
||||||
python_version: PythonVersion,
|
|
||||||
error_type: &str,
|
|
||||||
) {
|
|
||||||
let snapshot = format!("semantic_syntax_error_{error_type}_{name}_{python_version}");
|
|
||||||
let diagnostics = test_snippet_syntax_errors(
|
|
||||||
contents,
|
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
rules: settings::rule_table::RuleTable::empty(),
|
rules: settings::rule_table::RuleTable::empty(),
|
||||||
unresolved_target_version: python_version.into(),
|
unresolved_target_version: python_version.into(),
|
||||||
|
|
@ -1182,7 +1000,11 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_diagnostics!(snapshot, diagnostics);
|
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
|
||||||
|
assert_diagnostics!(format!("{snapshot}"), diagnostics);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(PythonVersion::PY310)]
|
#[test_case(PythonVersion::PY310)]
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ expression: content
|
||||||
---
|
---
|
||||||
syntax_errors.py:
|
syntax_errors.py:
|
||||||
1:15 invalid-syntax: Expected one or more symbol names after import
|
1:15 invalid-syntax: Expected one or more symbol names after import
|
||||||
3:12 invalid-syntax: Expected ')', found newline
|
3:12 invalid-syntax: Expected `)`, found newline
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ mod tests {
|
||||||
for rule in Rule::iter() {
|
for rule in Rule::iter() {
|
||||||
let (code, group) = (rule.noqa_code(), rule.group());
|
let (code, group) = (rule.noqa_code(), rule.group());
|
||||||
|
|
||||||
if matches!(group, RuleGroup::Removed) {
|
if matches!(group, RuleGroup::Removed { .. }) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,15 +209,15 @@ impl RuleSelector {
|
||||||
self.all_rules().filter(move |rule| {
|
self.all_rules().filter(move |rule| {
|
||||||
match rule.group() {
|
match rule.group() {
|
||||||
// Always include stable rules
|
// Always include stable rules
|
||||||
RuleGroup::Stable => true,
|
RuleGroup::Stable { .. } => true,
|
||||||
// Enabling preview includes all preview rules unless explicit selection is turned on
|
// Enabling preview includes all preview rules unless explicit selection is turned on
|
||||||
RuleGroup::Preview => {
|
RuleGroup::Preview { .. } => {
|
||||||
preview_enabled && (self.is_exact() || !preview_require_explicit)
|
preview_enabled && (self.is_exact() || !preview_require_explicit)
|
||||||
}
|
}
|
||||||
// Deprecated rules are excluded by default unless explicitly selected
|
// Deprecated rules are excluded by default unless explicitly selected
|
||||||
RuleGroup::Deprecated => !preview_enabled && self.is_exact(),
|
RuleGroup::Deprecated { .. } => !preview_enabled && self.is_exact(),
|
||||||
// Removed rules are included if explicitly selected but will error downstream
|
// Removed rules are included if explicitly selected but will error downstream
|
||||||
RuleGroup::Removed => self.is_exact(),
|
RuleGroup::Removed { .. } => self.is_exact(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ use crate::checkers::ast::Checker;
|
||||||
/// dag = DAG(dag_id="my_dag", schedule=timedelta(days=1))
|
/// dag = DAG(dag_id="my_dag", schedule=timedelta(days=1))
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.13.0")]
|
||||||
pub(crate) struct AirflowDagNoScheduleArgument;
|
pub(crate) struct AirflowDagNoScheduleArgument;
|
||||||
|
|
||||||
impl Violation for AirflowDagNoScheduleArgument {
|
impl Violation for AirflowDagNoScheduleArgument {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ use crate::{FixAvailability, Violation};
|
||||||
/// fab_auth_manager_app = FabAuthManager().get_fastapi_app()
|
/// fab_auth_manager_app = FabAuthManager().get_fastapi_app()
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.13.0")]
|
||||||
pub(crate) struct Airflow3MovedToProvider<'a> {
|
pub(crate) struct Airflow3MovedToProvider<'a> {
|
||||||
deprecated: QualifiedName<'a>,
|
deprecated: QualifiedName<'a>,
|
||||||
replacement: ProviderReplacement,
|
replacement: ProviderReplacement,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ use ruff_text_size::TextRange;
|
||||||
/// yesterday = today - timedelta(days=1)
|
/// yesterday = today - timedelta(days=1)
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.13.0")]
|
||||||
pub(crate) struct Airflow3Removal {
|
pub(crate) struct Airflow3Removal {
|
||||||
deprecated: String,
|
deprecated: String,
|
||||||
replacement: Replacement,
|
replacement: Replacement,
|
||||||
|
|
@ -195,6 +196,7 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||||
match qualified_name.segments() {
|
match qualified_name.segments() {
|
||||||
["airflow", .., "DAG" | "dag"] => {
|
["airflow", .., "DAG" | "dag"] => {
|
||||||
// with replacement
|
// with replacement
|
||||||
|
diagnostic_for_argument(checker, arguments, "concurrency", Some("max_active_tasks"));
|
||||||
diagnostic_for_argument(checker, arguments, "fail_stop", Some("fail_fast"));
|
diagnostic_for_argument(checker, arguments, "fail_stop", Some("fail_fast"));
|
||||||
diagnostic_for_argument(checker, arguments, "schedule_interval", Some("schedule"));
|
diagnostic_for_argument(checker, arguments, "schedule_interval", Some("schedule"));
|
||||||
diagnostic_for_argument(checker, arguments, "timetable", Some("schedule"));
|
diagnostic_for_argument(checker, arguments, "timetable", Some("schedule"));
|
||||||
|
|
@ -491,6 +493,12 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
|
||||||
"collected_datasets" => Replacement::AttrName("collected_assets"),
|
"collected_datasets" => Replacement::AttrName("collected_assets"),
|
||||||
_ => return,
|
_ => return,
|
||||||
},
|
},
|
||||||
|
["airflow", "models", "dag", "DAG"] | ["airflow", "models", "DAG"] | ["airflow", "DAG"] => {
|
||||||
|
match attr.as_str() {
|
||||||
|
"create_dagrun" => Replacement::None,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||||
"initialize_providers_dataset_uri_resources" => {
|
"initialize_providers_dataset_uri_resources" => {
|
||||||
Replacement::AttrName("initialize_providers_asset_uri_resources")
|
Replacement::AttrName("initialize_providers_asset_uri_resources")
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ use ruff_text_size::TextRange;
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.13.0")]
|
||||||
pub(crate) struct Airflow3SuggestedToMoveToProvider<'a> {
|
pub(crate) struct Airflow3SuggestedToMoveToProvider<'a> {
|
||||||
deprecated: QualifiedName<'a>,
|
deprecated: QualifiedName<'a>,
|
||||||
replacement: ProviderReplacement,
|
replacement: ProviderReplacement,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ use ruff_text_size::TextRange;
|
||||||
/// Asset(uri="test://test/")
|
/// Asset(uri="test://test/")
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.13.0")]
|
||||||
pub(crate) struct Airflow3SuggestedUpdate {
|
pub(crate) struct Airflow3SuggestedUpdate {
|
||||||
deprecated: String,
|
deprecated: String,
|
||||||
replacement: Replacement,
|
replacement: Replacement,
|
||||||
|
|
@ -261,9 +262,14 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||||
name: (*rest).to_string(),
|
name: (*rest).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["airflow", "models", "Param"] => Replacement::Rename {
|
[
|
||||||
|
"airflow",
|
||||||
|
"models",
|
||||||
|
..,
|
||||||
|
rest @ ("Param" | "ParamsDict" | "DagParam"),
|
||||||
|
] => Replacement::SourceModuleMoved {
|
||||||
module: "airflow.sdk.definitions.param",
|
module: "airflow.sdk.definitions.param",
|
||||||
name: "Param",
|
name: (*rest).to_string(),
|
||||||
},
|
},
|
||||||
|
|
||||||
// airflow.models.baseoperator
|
// airflow.models.baseoperator
|
||||||
|
|
@ -282,10 +288,12 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||||
},
|
},
|
||||||
|
|
||||||
// airflow.model..DAG
|
// airflow.model..DAG
|
||||||
["airflow", "models", .., "DAG"] => Replacement::SourceModuleMoved {
|
["airflow", "models", "dag", "DAG"] | ["airflow", "models", "DAG"] | ["airflow", "DAG"] => {
|
||||||
module: "airflow.sdk",
|
Replacement::SourceModuleMoved {
|
||||||
name: "DAG".to_string(),
|
module: "airflow.sdk",
|
||||||
},
|
name: "DAG".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// airflow.sensors.base
|
// airflow.sensors.base
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use crate::checkers::ast::Checker;
|
||||||
/// my_task = PythonOperator(task_id="my_task")
|
/// my_task = PythonOperator(task_id="my_task")
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "v0.0.271")]
|
||||||
pub(crate) struct AirflowVariableNameTaskIdMismatch {
|
pub(crate) struct AirflowVariableNameTaskIdMismatch {
|
||||||
task_id: String,
|
task_id: String,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ AIR301 [*] `timetable` is removed in Airflow 3.0
|
||||||
22 |
|
22 |
|
||||||
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
24 |
|
||||||
|
25 | DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
|
|
|
||||||
help: Use `schedule` instead
|
help: Use `schedule` instead
|
||||||
20 |
|
20 |
|
||||||
|
|
@ -36,249 +38,271 @@ help: Use `schedule` instead
|
||||||
- DAG(dag_id="class_timetable", timetable=NullTimetable())
|
- DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||||
23 + DAG(dag_id="class_timetable", schedule=NullTimetable())
|
23 + DAG(dag_id="class_timetable", schedule=NullTimetable())
|
||||||
24 |
|
24 |
|
||||||
25 |
|
25 | DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
26 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
26 |
|
||||||
|
|
||||||
AIR301 [*] `fail_stop` is removed in Airflow 3.0
|
AIR301 [*] `concurrency` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:26:31
|
--> AIR301_args.py:25:33
|
||||||
|
|
|
|
||||||
26 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
|
||||||
| ^^^^^^^^^
|
|
||||||
27 |
|
|
||||||
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
|
||||||
|
|
|
||||||
help: Use `fail_fast` instead
|
|
||||||
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||||
24 |
|
24 |
|
||||||
25 |
|
25 | DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
26 |
|
||||||
|
27 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
|
|
|
||||||
|
help: Use `max_active_tasks` instead
|
||||||
|
22 |
|
||||||
|
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||||
|
24 |
|
||||||
|
- DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
25 + DAG(dag_id="class_concurrency", max_active_tasks=12)
|
||||||
|
26 |
|
||||||
|
27 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
|
28 |
|
||||||
|
|
||||||
|
AIR301 [*] `fail_stop` is removed in Airflow 3.0
|
||||||
|
--> AIR301_args.py:27:31
|
||||||
|
|
|
||||||
|
25 | DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
26 |
|
||||||
|
27 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
|
| ^^^^^^^^^
|
||||||
|
28 |
|
||||||
|
29 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
||||||
|
|
|
||||||
|
help: Use `fail_fast` instead
|
||||||
|
24 |
|
||||||
|
25 | DAG(dag_id="class_concurrency", concurrency=12)
|
||||||
|
26 |
|
||||||
- DAG(dag_id="class_fail_stop", fail_stop=True)
|
- DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
26 + DAG(dag_id="class_fail_stop", fail_fast=True)
|
27 + DAG(dag_id="class_fail_stop", fail_fast=True)
|
||||||
27 |
|
28 |
|
||||||
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
29 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
||||||
29 |
|
30 |
|
||||||
|
|
||||||
AIR301 `default_view` is removed in Airflow 3.0
|
AIR301 `default_view` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:28:34
|
--> AIR301_args.py:29:34
|
||||||
|
|
|
|
||||||
26 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
27 | DAG(dag_id="class_fail_stop", fail_stop=True)
|
||||||
27 |
|
28 |
|
||||||
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
29 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
29 |
|
30 |
|
||||||
30 | DAG(dag_id="class_orientation", orientation="BT")
|
31 | DAG(dag_id="class_orientation", orientation="BT")
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 `orientation` is removed in Airflow 3.0
|
AIR301 `orientation` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:30:33
|
--> AIR301_args.py:31:33
|
||||||
|
|
|
|
||||||
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
29 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
||||||
29 |
|
30 |
|
||||||
30 | DAG(dag_id="class_orientation", orientation="BT")
|
31 | DAG(dag_id="class_orientation", orientation="BT")
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
31 |
|
32 |
|
||||||
32 | allow_future_exec_dates_dag = DAG(dag_id="class_allow_future_exec_dates")
|
33 | allow_future_exec_dates_dag = DAG(dag_id="class_allow_future_exec_dates")
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 [*] `schedule_interval` is removed in Airflow 3.0
|
AIR301 [*] `schedule_interval` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:41:6
|
--> AIR301_args.py:42:6
|
||||||
|
|
|
|
||||||
41 | @dag(schedule_interval="0 * * * *")
|
42 | @dag(schedule_interval="0 * * * *")
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
42 | def decorator_schedule_interval():
|
43 | def decorator_schedule_interval():
|
||||||
43 | pass
|
44 | pass
|
||||||
|
|
|
|
||||||
help: Use `schedule` instead
|
help: Use `schedule` instead
|
||||||
38 | pass
|
39 | pass
|
||||||
39 |
|
|
||||||
40 |
|
40 |
|
||||||
|
41 |
|
||||||
- @dag(schedule_interval="0 * * * *")
|
- @dag(schedule_interval="0 * * * *")
|
||||||
41 + @dag(schedule="0 * * * *")
|
42 + @dag(schedule="0 * * * *")
|
||||||
42 | def decorator_schedule_interval():
|
43 | def decorator_schedule_interval():
|
||||||
43 | pass
|
44 | pass
|
||||||
44 |
|
45 |
|
||||||
|
|
||||||
AIR301 [*] `timetable` is removed in Airflow 3.0
|
AIR301 [*] `timetable` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:46:6
|
--> AIR301_args.py:47:6
|
||||||
|
|
|
|
||||||
46 | @dag(timetable=NullTimetable())
|
47 | @dag(timetable=NullTimetable())
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
47 | def decorator_timetable():
|
48 | def decorator_timetable():
|
||||||
48 | pass
|
49 | pass
|
||||||
|
|
|
|
||||||
help: Use `schedule` instead
|
help: Use `schedule` instead
|
||||||
43 | pass
|
44 | pass
|
||||||
44 |
|
|
||||||
45 |
|
45 |
|
||||||
|
46 |
|
||||||
- @dag(timetable=NullTimetable())
|
- @dag(timetable=NullTimetable())
|
||||||
46 + @dag(schedule=NullTimetable())
|
47 + @dag(schedule=NullTimetable())
|
||||||
47 | def decorator_timetable():
|
48 | def decorator_timetable():
|
||||||
48 | pass
|
49 | pass
|
||||||
49 |
|
50 |
|
||||||
|
|
||||||
AIR301 [*] `execution_date` is removed in Airflow 3.0
|
AIR301 [*] `execution_date` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:54:62
|
--> AIR301_args.py:55:62
|
||||||
|
|
|
|
||||||
52 | def decorator_deprecated_operator_args():
|
53 | def decorator_deprecated_operator_args():
|
||||||
53 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
54 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||||
54 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
55 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
| ^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
55 | )
|
56 | )
|
||||||
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
57 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||||
|
|
|
|
||||||
help: Use `logical_date` instead
|
help: Use `logical_date` instead
|
||||||
51 | @dag()
|
52 | @dag()
|
||||||
52 | def decorator_deprecated_operator_args():
|
53 | def decorator_deprecated_operator_args():
|
||||||
53 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
54 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||||
- task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
- task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
54 + task_id="trigger_dagrun_op1", trigger_dag_id="test", logical_date="2024-12-04"
|
55 + task_id="trigger_dagrun_op1", trigger_dag_id="test", logical_date="2024-12-04"
|
||||||
55 | )
|
56 | )
|
||||||
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
57 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||||
57 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
58 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
|
|
||||||
AIR301 [*] `execution_date` is removed in Airflow 3.0
|
AIR301 [*] `execution_date` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:57:62
|
--> AIR301_args.py:58:62
|
||||||
|
|
|
|
||||||
55 | )
|
56 | )
|
||||||
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
57 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||||
57 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
58 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
| ^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
58 | )
|
59 | )
|
||||||
|
|
|
|
||||||
help: Use `logical_date` instead
|
help: Use `logical_date` instead
|
||||||
54 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
55 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
55 | )
|
56 | )
|
||||||
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
57 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||||
- task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
- task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
|
||||||
57 + task_id="trigger_dagrun_op2", trigger_dag_id="test", logical_date="2024-12-04"
|
58 + task_id="trigger_dagrun_op2", trigger_dag_id="test", logical_date="2024-12-04"
|
||||||
58 | )
|
59 | )
|
||||||
59 |
|
60 |
|
||||||
60 | branch_dt_op = datetime.BranchDateTimeOperator(
|
61 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||||
|
|
||||||
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:61:33
|
--> AIR301_args.py:62:33
|
||||||
|
|
|
|
||||||
60 | branch_dt_op = datetime.BranchDateTimeOperator(
|
61 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||||
61 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
62 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
62 | )
|
63 | )
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
|
|
|
|
||||||
help: Use `use_task_logical_date` instead
|
help: Use `use_task_logical_date` instead
|
||||||
58 | )
|
59 | )
|
||||||
59 |
|
60 |
|
||||||
60 | branch_dt_op = datetime.BranchDateTimeOperator(
|
61 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||||
- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||||
61 + task_id="branch_dt_op", use_task_logical_date=True, task_concurrency=5
|
62 + task_id="branch_dt_op", use_task_logical_date=True, task_concurrency=5
|
||||||
62 | )
|
63 | )
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
64 | task_id="branch_dt_op2",
|
65 | task_id="branch_dt_op2",
|
||||||
|
|
||||||
AIR301 [*] `task_concurrency` is removed in Airflow 3.0
|
AIR301 [*] `task_concurrency` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:61:62
|
--> AIR301_args.py:62:62
|
||||||
|
|
|
|
||||||
60 | branch_dt_op = datetime.BranchDateTimeOperator(
|
61 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||||
61 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
62 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^
|
||||||
62 | )
|
63 | )
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
|
|
|
|
||||||
help: Use `max_active_tis_per_dag` instead
|
help: Use `max_active_tis_per_dag` instead
|
||||||
58 | )
|
59 | )
|
||||||
59 |
|
60 |
|
||||||
60 | branch_dt_op = datetime.BranchDateTimeOperator(
|
61 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||||
- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||||
61 + task_id="branch_dt_op", use_task_execution_day=True, max_active_tis_per_dag=5
|
62 + task_id="branch_dt_op", use_task_execution_day=True, max_active_tis_per_dag=5
|
||||||
62 | )
|
63 | )
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
64 | task_id="branch_dt_op2",
|
65 | task_id="branch_dt_op2",
|
||||||
|
|
||||||
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:65:9
|
--> AIR301_args.py:66:9
|
||||||
|
|
|
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
64 | task_id="branch_dt_op2",
|
65 | task_id="branch_dt_op2",
|
||||||
65 | use_task_execution_day=True,
|
66 | use_task_execution_day=True,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
66 | sla=timedelta(seconds=10),
|
67 | sla=timedelta(seconds=10),
|
||||||
67 | )
|
68 | )
|
||||||
|
|
|
|
||||||
help: Use `use_task_logical_date` instead
|
help: Use `use_task_logical_date` instead
|
||||||
62 | )
|
63 | )
|
||||||
63 | branch_dt_op2 = BranchDateTimeOperator(
|
64 | branch_dt_op2 = BranchDateTimeOperator(
|
||||||
64 | task_id="branch_dt_op2",
|
65 | task_id="branch_dt_op2",
|
||||||
- use_task_execution_day=True,
|
- use_task_execution_day=True,
|
||||||
65 + use_task_logical_date=True,
|
66 + use_task_logical_date=True,
|
||||||
66 | sla=timedelta(seconds=10),
|
67 | sla=timedelta(seconds=10),
|
||||||
67 | )
|
68 | )
|
||||||
68 |
|
69 |
|
||||||
|
|
||||||
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:92:9
|
--> AIR301_args.py:93:9
|
||||||
|
|
|
|
||||||
90 | follow_task_ids_if_true=None,
|
91 | follow_task_ids_if_true=None,
|
||||||
91 | week_day=1,
|
92 | week_day=1,
|
||||||
92 | use_task_execution_day=True,
|
93 | use_task_execution_day=True,
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
93 | )
|
94 | )
|
||||||
|
|
|
|
||||||
help: Use `use_task_logical_date` instead
|
help: Use `use_task_logical_date` instead
|
||||||
89 | follow_task_ids_if_false=None,
|
90 | follow_task_ids_if_false=None,
|
||||||
90 | follow_task_ids_if_true=None,
|
91 | follow_task_ids_if_true=None,
|
||||||
91 | week_day=1,
|
92 | week_day=1,
|
||||||
- use_task_execution_day=True,
|
- use_task_execution_day=True,
|
||||||
92 + use_task_logical_date=True,
|
93 + use_task_logical_date=True,
|
||||||
93 | )
|
94 | )
|
||||||
94 |
|
95 |
|
||||||
95 | trigger_dagrun_op >> trigger_dagrun_op2
|
96 | trigger_dagrun_op >> trigger_dagrun_op2
|
||||||
|
|
||||||
AIR301 `filename_template` is removed in Airflow 3.0
|
AIR301 `filename_template` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:102:15
|
--> AIR301_args.py:103:15
|
||||||
|
|
|
|
||||||
101 | # deprecated filename_template argument in FileTaskHandler
|
102 | # deprecated filename_template argument in FileTaskHandler
|
||||||
102 | S3TaskHandler(filename_template="/tmp/test")
|
103 | S3TaskHandler(filename_template="/tmp/test")
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
103 | HdfsTaskHandler(filename_template="/tmp/test")
|
104 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||||
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
105 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 `filename_template` is removed in Airflow 3.0
|
AIR301 `filename_template` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:103:17
|
--> AIR301_args.py:104:17
|
||||||
|
|
|
|
||||||
101 | # deprecated filename_template argument in FileTaskHandler
|
102 | # deprecated filename_template argument in FileTaskHandler
|
||||||
102 | S3TaskHandler(filename_template="/tmp/test")
|
103 | S3TaskHandler(filename_template="/tmp/test")
|
||||||
103 | HdfsTaskHandler(filename_template="/tmp/test")
|
104 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
105 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||||
105 | GCSTaskHandler(filename_template="/tmp/test")
|
106 | GCSTaskHandler(filename_template="/tmp/test")
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 `filename_template` is removed in Airflow 3.0
|
AIR301 `filename_template` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:104:26
|
--> AIR301_args.py:105:26
|
||||||
|
|
|
|
||||||
102 | S3TaskHandler(filename_template="/tmp/test")
|
103 | S3TaskHandler(filename_template="/tmp/test")
|
||||||
103 | HdfsTaskHandler(filename_template="/tmp/test")
|
104 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||||
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
105 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
105 | GCSTaskHandler(filename_template="/tmp/test")
|
106 | GCSTaskHandler(filename_template="/tmp/test")
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 `filename_template` is removed in Airflow 3.0
|
AIR301 `filename_template` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:105:16
|
--> AIR301_args.py:106:16
|
||||||
|
|
|
|
||||||
103 | HdfsTaskHandler(filename_template="/tmp/test")
|
104 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||||
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
105 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||||
105 | GCSTaskHandler(filename_template="/tmp/test")
|
106 | GCSTaskHandler(filename_template="/tmp/test")
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
106 |
|
107 |
|
||||||
107 | FabAuthManager(None)
|
108 | FabAuthManager(None)
|
||||||
|
|
|
|
||||||
|
|
||||||
AIR301 `appbuilder` is removed in Airflow 3.0
|
AIR301 `appbuilder` is removed in Airflow 3.0
|
||||||
--> AIR301_args.py:107:15
|
--> AIR301_args.py:108:15
|
||||||
|
|
|
|
||||||
105 | GCSTaskHandler(filename_template="/tmp/test")
|
106 | GCSTaskHandler(filename_template="/tmp/test")
|
||||||
106 |
|
107 |
|
||||||
107 | FabAuthManager(None)
|
108 | FabAuthManager(None)
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
|
|
||||||
help: The constructor takes no parameter now
|
help: The constructor takes no parameter now
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,25 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||||
---
|
---
|
||||||
|
AIR311 [*] `airflow.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_args.py:13:1
|
||||||
|
|
|
||||||
|
13 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback)
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
help: Use `DAG` from `airflow.sdk` instead.
|
||||||
|
2 |
|
||||||
|
3 | from datetime import timedelta
|
||||||
|
4 |
|
||||||
|
- from airflow import DAG, dag
|
||||||
|
5 + from airflow import dag
|
||||||
|
6 | from airflow.operators.datetime import BranchDateTimeOperator
|
||||||
|
7 + from airflow.sdk import DAG
|
||||||
|
8 |
|
||||||
|
9 |
|
||||||
|
10 | def sla_callback(*arg, **kwargs):
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
AIR311 `sla_miss_callback` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
AIR311 `sla_miss_callback` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||||
--> AIR311_args.py:13:34
|
--> AIR311_args.py:13:34
|
||||||
|
|
|
|
||||||
|
|
|
||||||
|
|
@ -737,79 +737,185 @@ AIR311 [*] `airflow.models.Param` is removed in Airflow 3.0; It still works in A
|
||||||
96 | # airflow.models
|
96 | # airflow.models
|
||||||
97 | Param()
|
97 | Param()
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
98 | DagParam()
|
||||||
|
99 | ParamsDict()
|
||||||
|
|
|
|
||||||
help: Use `Param` from `airflow.sdk.definitions.param` instead.
|
help: Use `Param` from `airflow.sdk.definitions.param` instead.
|
||||||
91 | task_decorator_factory()
|
91 | task_decorator_factory()
|
||||||
92 |
|
92 |
|
||||||
93 |
|
93 |
|
||||||
- from airflow.models import Param
|
- from airflow.models import DagParam, Param, ParamsDict
|
||||||
94 + from airflow.sdk.definitions.param import Param
|
94 + from airflow.models import DagParam, ParamsDict
|
||||||
95 |
|
95 + from airflow.sdk.definitions.param import Param
|
||||||
|
96 |
|
||||||
|
97 | # airflow.models
|
||||||
|
98 | Param()
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
AIR311 [*] `airflow.models.DagParam` 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:98:1
|
||||||
|
|
|
||||||
96 | # airflow.models
|
96 | # airflow.models
|
||||||
97 | Param()
|
97 | Param()
|
||||||
|
98 | DagParam()
|
||||||
|
| ^^^^^^^^
|
||||||
|
99 | ParamsDict()
|
||||||
|
|
|
||||||
|
help: Use `DagParam` from `airflow.sdk.definitions.param` instead.
|
||||||
|
91 | task_decorator_factory()
|
||||||
|
92 |
|
||||||
|
93 |
|
||||||
|
- from airflow.models import DagParam, Param, ParamsDict
|
||||||
|
94 + from airflow.models import Param, ParamsDict
|
||||||
|
95 + from airflow.sdk.definitions.param import DagParam
|
||||||
|
96 |
|
||||||
|
97 | # airflow.models
|
||||||
|
98 | Param()
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
AIR311 [*] `airflow.models.ParamsDict` 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:99:1
|
||||||
|
|
|
||||||
|
97 | Param()
|
||||||
|
98 | DagParam()
|
||||||
|
99 | ParamsDict()
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Use `ParamsDict` from `airflow.sdk.definitions.param` instead.
|
||||||
|
91 | task_decorator_factory()
|
||||||
|
92 |
|
||||||
|
93 |
|
||||||
|
- from airflow.models import DagParam, Param, ParamsDict
|
||||||
|
94 + from airflow.models import DagParam, Param
|
||||||
|
95 + from airflow.sdk.definitions.param import ParamsDict
|
||||||
|
96 |
|
||||||
|
97 | # airflow.models
|
||||||
|
98 | Param()
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
AIR311 [*] `airflow.models.param.Param` 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:105:1
|
||||||
|
|
|
||||||
|
104 | # airflow.models.param
|
||||||
|
105 | Param()
|
||||||
|
| ^^^^^
|
||||||
|
106 | DagParam()
|
||||||
|
107 | ParamsDict()
|
||||||
|
|
|
||||||
|
help: Use `Param` from `airflow.sdk.definitions.param` instead.
|
||||||
|
99 | ParamsDict()
|
||||||
|
100 |
|
||||||
|
101 |
|
||||||
|
- from airflow.models.param import DagParam, Param, ParamsDict
|
||||||
|
102 + from airflow.models.param import DagParam, ParamsDict
|
||||||
|
103 + from airflow.sdk.definitions.param import Param
|
||||||
|
104 |
|
||||||
|
105 | # airflow.models.param
|
||||||
|
106 | Param()
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
AIR311 [*] `airflow.models.param.DagParam` 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:106:1
|
||||||
|
|
|
||||||
|
104 | # airflow.models.param
|
||||||
|
105 | Param()
|
||||||
|
106 | DagParam()
|
||||||
|
| ^^^^^^^^
|
||||||
|
107 | ParamsDict()
|
||||||
|
|
|
||||||
|
help: Use `DagParam` from `airflow.sdk.definitions.param` instead.
|
||||||
|
99 | ParamsDict()
|
||||||
|
100 |
|
||||||
|
101 |
|
||||||
|
- from airflow.models.param import DagParam, Param, ParamsDict
|
||||||
|
102 + from airflow.models.param import Param, ParamsDict
|
||||||
|
103 + from airflow.sdk.definitions.param import DagParam
|
||||||
|
104 |
|
||||||
|
105 | # airflow.models.param
|
||||||
|
106 | Param()
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
AIR311 [*] `airflow.models.param.ParamsDict` 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:107:1
|
||||||
|
|
|
||||||
|
105 | Param()
|
||||||
|
106 | DagParam()
|
||||||
|
107 | ParamsDict()
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Use `ParamsDict` from `airflow.sdk.definitions.param` instead.
|
||||||
|
99 | ParamsDict()
|
||||||
|
100 |
|
||||||
|
101 |
|
||||||
|
- from airflow.models.param import DagParam, Param, ParamsDict
|
||||||
|
102 + from airflow.models.param import DagParam, Param
|
||||||
|
103 + from airflow.sdk.definitions.param import ParamsDict
|
||||||
|
104 |
|
||||||
|
105 | # airflow.models.param
|
||||||
|
106 | Param()
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
AIR311 [*] `airflow.sensors.base.BaseSensorOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
AIR311 [*] `airflow.sensors.base.BaseSensorOperator` 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:107:1
|
--> AIR311_names.py:117:1
|
||||||
|
|
|
|
||||||
106 | # airflow.sensors.base
|
116 | # airflow.sensors.base
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
108 | PokeReturnValue()
|
118 | PokeReturnValue()
|
||||||
109 | poke_mode_only()
|
119 | poke_mode_only()
|
||||||
|
|
|
|
||||||
help: Use `BaseSensorOperator` from `airflow.sdk` instead.
|
help: Use `BaseSensorOperator` from `airflow.sdk` instead.
|
||||||
98 |
|
108 |
|
||||||
99 |
|
109 |
|
||||||
100 | from airflow.sensors.base import (
|
110 | from airflow.sensors.base import (
|
||||||
- BaseSensorOperator,
|
- BaseSensorOperator,
|
||||||
101 | PokeReturnValue,
|
111 | PokeReturnValue,
|
||||||
102 | poke_mode_only,
|
112 | poke_mode_only,
|
||||||
103 | )
|
113 | )
|
||||||
104 + from airflow.sdk import BaseSensorOperator
|
114 + from airflow.sdk import BaseSensorOperator
|
||||||
105 |
|
115 |
|
||||||
106 | # airflow.sensors.base
|
116 | # airflow.sensors.base
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
AIR311 [*] `airflow.sensors.base.PokeReturnValue` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
AIR311 [*] `airflow.sensors.base.PokeReturnValue` 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:108:1
|
--> AIR311_names.py:118:1
|
||||||
|
|
|
|
||||||
106 | # airflow.sensors.base
|
116 | # airflow.sensors.base
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
108 | PokeReturnValue()
|
118 | PokeReturnValue()
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
109 | poke_mode_only()
|
119 | poke_mode_only()
|
||||||
|
|
|
|
||||||
help: Use `PokeReturnValue` from `airflow.sdk` instead.
|
help: Use `PokeReturnValue` from `airflow.sdk` instead.
|
||||||
99 |
|
109 |
|
||||||
100 | from airflow.sensors.base import (
|
110 | from airflow.sensors.base import (
|
||||||
101 | BaseSensorOperator,
|
111 | BaseSensorOperator,
|
||||||
- PokeReturnValue,
|
- PokeReturnValue,
|
||||||
102 | poke_mode_only,
|
112 | poke_mode_only,
|
||||||
103 | )
|
113 | )
|
||||||
104 + from airflow.sdk import PokeReturnValue
|
114 + from airflow.sdk import PokeReturnValue
|
||||||
105 |
|
115 |
|
||||||
106 | # airflow.sensors.base
|
116 | # airflow.sensors.base
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
AIR311 [*] `airflow.sensors.base.poke_mode_only` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
AIR311 [*] `airflow.sensors.base.poke_mode_only` 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:109:1
|
--> AIR311_names.py:119:1
|
||||||
|
|
|
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
108 | PokeReturnValue()
|
118 | PokeReturnValue()
|
||||||
109 | poke_mode_only()
|
119 | poke_mode_only()
|
||||||
| ^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
help: Use `poke_mode_only` from `airflow.sdk` instead.
|
help: Use `poke_mode_only` from `airflow.sdk` instead.
|
||||||
100 | from airflow.sensors.base import (
|
110 | from airflow.sensors.base import (
|
||||||
101 | BaseSensorOperator,
|
111 | BaseSensorOperator,
|
||||||
102 | PokeReturnValue,
|
112 | PokeReturnValue,
|
||||||
- poke_mode_only,
|
- poke_mode_only,
|
||||||
103 | )
|
113 | )
|
||||||
104 + from airflow.sdk import poke_mode_only
|
114 + from airflow.sdk import poke_mode_only
|
||||||
105 |
|
115 |
|
||||||
106 | # airflow.sensors.base
|
116 | # airflow.sensors.base
|
||||||
107 | BaseSensorOperator()
|
117 | BaseSensorOperator()
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ use crate::rules::eradicate::detection::comment_contains_code;
|
||||||
///
|
///
|
||||||
/// [#4845]: https://github.com/astral-sh/ruff/issues/4845
|
/// [#4845]: https://github.com/astral-sh/ruff/issues/4845
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "v0.0.145")]
|
||||||
pub(crate) struct CommentedOutCode;
|
pub(crate) struct CommentedOutCode;
|
||||||
|
|
||||||
impl Violation for CommentedOutCode {
|
impl Violation for CommentedOutCode {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ use ruff_python_ast::PythonVersion;
|
||||||
/// [typing-annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
|
/// [typing-annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
|
||||||
/// [typing-extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
/// [typing-extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.8.0")]
|
||||||
pub(crate) struct FastApiNonAnnotatedDependency {
|
pub(crate) struct FastApiNonAnnotatedDependency {
|
||||||
py_version: PythonVersion,
|
py_version: PythonVersion,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ use crate::{AlwaysFixableViolation, Fix};
|
||||||
/// return item
|
/// return item
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.8.0")]
|
||||||
pub(crate) struct FastApiRedundantResponseModel;
|
pub(crate) struct FastApiRedundantResponseModel;
|
||||||
|
|
||||||
impl AlwaysFixableViolation for FastApiRedundantResponseModel {
|
impl AlwaysFixableViolation for FastApiRedundantResponseModel {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ use crate::{FixAvailability, Violation};
|
||||||
/// This rule's fix is marked as unsafe, as modifying a function signature can
|
/// This rule's fix is marked as unsafe, as modifying a function signature can
|
||||||
/// change the behavior of the code.
|
/// change the behavior of the code.
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(stable_since = "0.10.0")]
|
||||||
pub(crate) struct FastApiUnusedPathParameter {
|
pub(crate) struct FastApiUnusedPathParameter {
|
||||||
arg_name: String,
|
arg_name: String,
|
||||||
function_name: String,
|
function_name: String,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue