Compare commits

..

1 Commits

537 changed files with 8653 additions and 223792 deletions

View File

@@ -7,6 +7,10 @@ serial = { max-threads = 1 }
filter = 'binary(file_watching)'
test-group = 'serial'
[[profile.default.overrides]]
filter = 'binary(e2e)'
test-group = 'serial'
[profile.ci]
# Print out output for failing tests as soon as they fail, and also at the end
# of the run (for easy scrollability).

View File

@@ -2,11 +2,12 @@
$schema: "https://docs.renovatebot.com/renovate-schema.json",
dependencyDashboard: true,
suppressNotifications: ["prEditedNotification"],
extends: ["github>astral-sh/renovate-config"],
extends: ["config:recommended"],
labels: ["internal"],
schedule: ["before 4am on Monday"],
semanticCommits: "disabled",
separateMajorMinor: false,
prHourlyLimit: 10,
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "pip_requirements", "npm"],
cargo: {
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
@@ -15,7 +16,7 @@
pep621: {
// The default for this package manager is to only search for `pyproject.toml` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
managerFilePatterns: ["^(python|scripts)/.*pyproject\\.toml$"],
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
},
pip_requirements: {
// The default for this package manager is to run on all requirements.txt files:
@@ -33,7 +34,7 @@
npm: {
// The default for this package manager is to only search for `package.json` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
managerFilePatterns: ["^playground/.*package\\.json$"],
fileMatch: ["^playground/.*package\\.json$"],
},
"pre-commit": {
enabled: true,

View File

@@ -43,7 +43,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -72,7 +72,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -114,7 +114,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: arm64
@@ -170,7 +170,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.platform.arch }}
@@ -223,7 +223,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -300,7 +300,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -365,7 +365,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -431,7 +431,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"

View File

@@ -230,7 +230,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -252,7 +252,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -261,15 +261,15 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
@@ -284,10 +284,6 @@ jobs:
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
- name: Dogfood ty on py-fuzzer
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
- name: Dogfood ty on the scripts directory
run: uv run --project=./scripts cargo run -p ty check --project=./scripts
- name: Dogfood ty on ty_benchmark
run: uv run --project=./scripts/ty_benchmark cargo run -p ty check --project=./scripts/ty_benchmark
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -315,7 +311,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -323,11 +319,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
enable-cache: "true"
- name: "Run tests"
@@ -350,17 +346,17 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
enable-cache: "true"
- name: "Run tests"
@@ -378,7 +374,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -415,7 +411,7 @@ jobs:
with:
file: "Cargo.toml"
field: "workspace.package.rust-version"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -439,7 +435,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "fuzz -> target"
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -448,7 +444,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- name: "Install cargo-fuzz"
# 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
@@ -466,8 +462,8 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -498,10 +494,10 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -536,7 +532,7 @@ jobs:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
@@ -547,7 +543,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -642,8 +638,8 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -688,7 +684,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
- uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -701,8 +697,8 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -723,11 +719,11 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Prep README.md"
@@ -752,8 +748,8 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
@@ -785,7 +781,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Add SSH key"
@@ -796,7 +792,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
python-version: 3.13
activate-environment: true
@@ -829,7 +825,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -857,7 +853,7 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -875,7 +871,7 @@ jobs:
repository: "astral-sh/ruff-lsp"
path: ruff-lsp
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
# installation fails on 3.13 and newer
python-version: "3.12"
@@ -908,7 +904,7 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
@@ -918,7 +914,7 @@ jobs:
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Build playgrounds"
run: npm run dev:wasm
@@ -942,25 +938,22 @@ jobs:
needs.determine_changes.outputs.linter == 'true'
)
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-codspeed
@@ -968,10 +961,11 @@ jobs:
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"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
with:
mode: simulation
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-instrumented-ty:
name: "benchmarks instrumented (ty)"
@@ -984,25 +978,22 @@ jobs:
needs.determine_changes.outputs.ty == 'true'
)
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-codspeed
@@ -1010,10 +1001,11 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
with:
mode: simulation
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
@@ -1021,9 +1013,6 @@ jobs:
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
matrix:
benchmarks:
@@ -1035,16 +1024,16 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-codspeed
@@ -1052,7 +1041,7 @@ jobs:
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now
@@ -1061,3 +1050,4 @@ jobs:
with:
mode: walltime
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -34,12 +34,12 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: Build ruff
# A debug build means the script runs slower once it gets started,
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI

View File

@@ -43,9 +43,9 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
@@ -81,9 +81,9 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"

View File

@@ -28,7 +28,7 @@ jobs:
ref: ${{ inputs.ref }}
persist-credentials: true
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: 3.12
@@ -68,7 +68,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}

View File

@@ -37,7 +37,7 @@ jobs:
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Run TypeScript checks"
run: npm run check

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: wheels-*

View File

@@ -41,7 +41,7 @@ jobs:
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Run TypeScript checks"
run: npm run check

View File

@@ -60,7 +60,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -123,7 +123,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -174,7 +174,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -250,7 +250,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive

View File

@@ -77,7 +77,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -131,7 +131,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -170,7 +170,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -198,7 +198,7 @@ jobs:
run: |
rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Rust toolchain"
if: ${{ success() }}
run: rustup show
@@ -207,12 +207,12 @@ jobs:
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
with:
tool: cargo-insta
- name: Update snapshots

View File

@@ -33,11 +33,11 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
@@ -67,7 +67,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
ecosystem-analyzer \
--repository ruff \

View File

@@ -29,11 +29,11 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
@@ -52,7 +52,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
ecosystem-analyzer \
--verbose \

View File

@@ -45,7 +45,7 @@ jobs:
path: typing
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"

View File

@@ -5,6 +5,5 @@
"rust-analyzer.check.command": "clippy",
"search.exclude": {
"**/*.snap": true
},
"ty.diagnosticMode": "openFilesOnly"
}
}

View File

@@ -1,69 +1,5 @@
# Changelog
## 0.14.8
Released on 2025-12-04.
### Preview features
- \[`flake8-bugbear`\] Catch `yield` expressions within other statements (`B901`) ([#21200](https://github.com/astral-sh/ruff/pull/21200))
- \[`flake8-use-pathlib`\] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#21440](https://github.com/astral-sh/ruff/pull/21440))
### Bug fixes
- Fix syntax error false positives for `await` outside functions ([#21763](https://github.com/astral-sh/ruff/pull/21763))
- \[`flake8-simplify`\] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) ([#21479](https://github.com/astral-sh/ruff/pull/21479))
### Documentation
- Suggest using `--output-file` option in GitLab integration ([#21706](https://github.com/astral-sh/ruff/pull/21706))
### Other changes
- [syntax-error] Default type parameter followed by non-default type parameter ([#21657](https://github.com/astral-sh/ruff/pull/21657))
### Contributors
- [@kieran-ryan](https://github.com/kieran-ryan)
- [@11happy](https://github.com/11happy)
- [@danparizher](https://github.com/danparizher)
- [@ntBre](https://github.com/ntBre)
## 0.14.7
Released on 2025-11-28.
### Preview features
- \[`flake8-bandit`\] Handle string literal bindings in suspicious-url-open-usage (`S310`) ([#21469](https://github.com/astral-sh/ruff/pull/21469))
- \[`pylint`\] Fix `PLR1708` false positives on nested functions ([#21177](https://github.com/astral-sh/ruff/pull/21177))
- \[`pylint`\] Fix suppression for empty dict without tuple key annotation (`PLE1141`) ([#21290](https://github.com/astral-sh/ruff/pull/21290))
- \[`ruff`\] Add rule `RUF066` to detect unnecessary class properties ([#21535](https://github.com/astral-sh/ruff/pull/21535))
- \[`ruff`\] Catch more dummy variable uses (`RUF052`) ([#19799](https://github.com/astral-sh/ruff/pull/19799))
### Bug fixes
- [server] Set severity for non-rule diagnostics ([#21559](https://github.com/astral-sh/ruff/pull/21559))
- \[`flake8-implicit-str-concat`\] Avoid invalid fix in (`ISC003`) ([#21517](https://github.com/astral-sh/ruff/pull/21517))
- \[`parser`\] Fix panic when parsing IPython escape command expressions ([#21480](https://github.com/astral-sh/ruff/pull/21480))
### CLI
- Show partial fixability indicator in statistics output ([#21513](https://github.com/astral-sh/ruff/pull/21513))
### Contributors
- [@mikeleppane](https://github.com/mikeleppane)
- [@senekor](https://github.com/senekor)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@JumboBear](https://github.com/JumboBear)
- [@prakhar1144](https://github.com/prakhar1144)
- [@tsvikas](https://github.com/tsvikas)
- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@MichaReiser](https://github.com/MichaReiser)
## 0.14.6
Released on 2025-11-21.

73
Cargo.lock generated
View File

@@ -442,9 +442,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.53"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
"clap_derive",
@@ -452,9 +452,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.53"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -1016,7 +1016,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -1108,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -1255,7 +1255,7 @@ checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
dependencies = [
"compact_str",
"get-size-derive2",
"hashbrown 0.16.1",
"hashbrown 0.16.0",
"indexmap",
"smallvec",
]
@@ -1353,9 +1353,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.1"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
dependencies = [
"equivalent",
]
@@ -1564,12 +1564,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.1"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"hashbrown 0.16.0",
"serde",
"serde_core",
]
@@ -1763,7 +1763,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -2859,7 +2859,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.14.8"
version = "0.14.6"
dependencies = [
"anyhow",
"argfile",
@@ -3117,18 +3117,17 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.8"
version = "0.14.6"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.10.0",
"clap",
"colored 3.0.0",
"compact_str",
"fern",
"glob",
"globset",
"hashbrown 0.16.1",
"hashbrown 0.16.0",
"imperative",
"insta",
"is-macro",
@@ -3473,7 +3472,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.8"
version = "0.14.6"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3571,7 +3570,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -3589,7 +3588,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
dependencies = [
"boxcar",
"compact_str",
@@ -3613,12 +3612,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
[[package]]
name = "salsa-macros"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
dependencies = [
"proc-macro2",
"quote",
@@ -3936,9 +3935,9 @@ checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
[[package]]
name = "syn"
version = "2.0.111"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
@@ -3972,7 +3971,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -4217,9 +4216,9 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]]
name = "tracing"
version = "0.1.43"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
@@ -4229,9 +4228,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.31"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
@@ -4240,9 +4239,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.35"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
"valuable",
@@ -4284,9 +4283,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"chrono",
"matchers",
@@ -4463,7 +4462,7 @@ dependencies = [
"drop_bomb",
"get-size2",
"glob",
"hashbrown 0.16.1",
"hashbrown 0.16.0",
"indexmap",
"indoc",
"insta",
@@ -4475,7 +4474,6 @@ dependencies = [
"quickcheck_macros",
"ruff_annotate_snippets",
"ruff_db",
"ruff_diagnostics",
"ruff_index",
"ruff_macros",
"ruff_memory_usage",
@@ -4521,7 +4519,6 @@ dependencies = [
"lsp-types",
"regex",
"ruff_db",
"ruff_diagnostics",
"ruff_macros",
"ruff_notebook",
"ruff_python_ast",
@@ -4562,7 +4559,6 @@ dependencies = [
"path-slash",
"regex",
"ruff_db",
"ruff_diagnostics",
"ruff_index",
"ruff_notebook",
"ruff_python_ast",
@@ -4604,7 +4600,6 @@ dependencies = [
"js-sys",
"log",
"ruff_db",
"ruff_diagnostics",
"ruff_notebook",
"ruff_python_formatter",
"ruff_source_file",
@@ -5025,7 +5020,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]

View File

@@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",

View File

@@ -147,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.14.8/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.8/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.14.6/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.6/install.ps1 | iex"
```
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
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.8
rev: v0.14.6
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.8"
version = "0.14.6"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -34,21 +34,9 @@ struct ExpandedStatistics<'a> {
code: Option<&'a SecondaryCode>,
name: &'static str,
count: usize,
#[serde(rename = "fixable")]
all_fixable: bool,
fixable_count: usize,
fixable: bool,
}
impl ExpandedStatistics<'_> {
fn any_fixable(&self) -> bool {
self.fixable_count > 0
}
}
/// Accumulator type for grouping diagnostics by code.
/// Format: (`code`, `representative_diagnostic`, `total_count`, `fixable_count`)
type DiagnosticGroup<'a> = (Option<&'a SecondaryCode>, &'a Diagnostic, usize, usize);
pub(crate) struct Printer {
format: OutputFormat,
log_level: LogLevel,
@@ -145,7 +133,7 @@ impl Printer {
if fixables.applicable > 0 {
writeln!(
writer,
"{fix_prefix} {} fixable with the `--fix` option.",
"{fix_prefix} {} fixable with the --fix option.",
fixables.applicable
)?;
}
@@ -268,41 +256,35 @@ impl Printer {
diagnostics: &Diagnostics,
writer: &mut dyn Write,
) -> Result<()> {
let required_applicability = self.unsafe_fixes.required_applicability();
let statistics: Vec<ExpandedStatistics> = diagnostics
.inner
.iter()
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
.fold(vec![], |mut acc: Vec<DiagnosticGroup>, diagnostic| {
let is_fixable = diagnostic
.fix()
.is_some_and(|fix| fix.applies(required_applicability));
let code = diagnostic.secondary_code();
if let Some((prev_code, _prev_message, count, fixable_count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
if is_fixable {
*fixable_count += 1;
.map(|message| (message.secondary_code(), message))
.sorted_by_key(|(code, message)| (*code, message.fixable()))
.fold(
vec![],
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
return acc;
}
return acc;
}
}
acc.push((code, diagnostic, 1, usize::from(is_fixable)));
acc
})
.iter()
.map(
|&(code, message, count, fixable_count)| ExpandedStatistics {
code,
name: message.name(),
count,
// Backward compatibility: `fixable` is true only when all violations are fixable.
// See: https://github.com/astral-sh/ruff/pull/21513
all_fixable: fixable_count == count,
fixable_count,
acc.push(((code, message), 1));
acc
},
)
.iter()
.map(|&((code, message), count)| ExpandedStatistics {
code,
name: message.name(),
count,
fixable: if let Some(fix) = message.fix() {
fix.applies(self.unsafe_fixes.required_applicability())
} else {
false
},
})
.sorted_by_key(|statistic| Reverse(statistic.count))
.collect();
@@ -326,14 +308,13 @@ impl Printer {
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
.max()
.unwrap();
let any_fixable = statistics.iter().any(ExpandedStatistics::any_fixable);
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
let all_fixable = format!("[{}] ", "*".cyan());
let partially_fixable = format!("[{}] ", "-".cyan());
let fixable = format!("[{}] ", "*".cyan());
let unfixable = "[ ] ";
// By default, we mimic Flake8's `--statistics` format.
for statistic in &statistics {
for statistic in statistics {
writeln!(
writer,
"{:>count_width$}\t{:<code_width$}\t{}{}",
@@ -345,10 +326,8 @@ impl Printer {
.red()
.bold(),
if any_fixable {
if statistic.all_fixable {
&all_fixable
} else if statistic.any_fixable() {
&partially_fixable
if statistic.fixable {
&fixable
} else {
unfixable
}

View File

@@ -1043,7 +1043,7 @@ def mvce(keys, values):
----- stdout -----
1 C416 [*] unnecessary-comprehension
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
");
@@ -1073,8 +1073,7 @@ def mvce(keys, values):
"code": "C416",
"name": "unnecessary-comprehension",
"count": 1,
"fixable": false,
"fixable_count": 0
"fixable": false
}
]
@@ -1107,8 +1106,7 @@ def mvce(keys, values):
"code": "C416",
"name": "unnecessary-comprehension",
"count": 1,
"fixable": true,
"fixable_count": 1
"fixable": true
}
]
@@ -1116,54 +1114,6 @@ def mvce(keys, values):
"#);
}
#[test]
fn show_statistics_json_partial_fix() {
let mut cmd = RuffCheck::default()
.args([
"--select",
"UP035",
"--statistics",
"--output-format",
"json",
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("from typing import List, AsyncGenerator"), @r#"
success: false
exit_code: 1
----- stdout -----
[
{
"code": "UP035",
"name": "deprecated-import",
"count": 2,
"fixable": false,
"fixable_count": 1
}
]
----- stderr -----
"#);
}
#[test]
fn show_statistics_partial_fix() {
let mut cmd = RuffCheck::default()
.args(["--select", "UP035", "--statistics"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("from typing import List, AsyncGenerator"), @r"
success: false
exit_code: 1
----- stdout -----
2 UP035 [-] deprecated-import
Found 2 errors.
[*] 1 fixable with the `--fix` option.
----- stderr -----
");
}
#[test]
fn show_statistics_syntax_errors() {
let mut cmd = RuffCheck::default()
@@ -1860,7 +1810,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
--> -:1:1
Found 2 errors.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
");
@@ -1903,7 +1853,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
--> -:1:1
Found 2 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
");

View File

@@ -59,6 +59,8 @@ divan = { workspace = true, optional = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
criterion = { workspace = true, default-features = false, optional = true }
rayon = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
@@ -86,7 +88,3 @@ mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
tikv-jemallocator = { workspace = true }
[dev-dependencies]
rustc-hash = { workspace = true }
rayon = { workspace = true }

View File

@@ -6,8 +6,7 @@ use criterion::{
use ruff_benchmark::{
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
};
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::{Mode, lexer};
use ruff_python_parser::{Mode, TokenKind, lexer};
#[cfg(target_os = "windows")]
#[global_allocator]

View File

@@ -120,7 +120,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
1070,
600,
);
static FREQTRADE: Benchmark = Benchmark::new(
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
950,
900,
);
#[track_caller]

View File

@@ -354,13 +354,6 @@ impl Diagnostic {
Arc::make_mut(&mut self.inner).fix = Some(fix);
}
/// If `fix` is `Some`, set the fix for this diagnostic.
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
if let Some(fix) = fix {
self.set_fix(fix);
}
}
/// Remove the fix for this diagnostic.
pub fn remove_fix(&mut self) {
Arc::make_mut(&mut self.inner).fix = None;

View File

@@ -21,11 +21,7 @@ use crate::source::source_text;
/// reflected in the changed AST offsets.
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
/// for determining if a query result is unchanged.
///
/// The LRU capacity of 200 was picked without any empirical evidence that it's optimal,
/// instead it's a wild guess that it should be unlikely that incremental changes involve
/// more than 200 modules. Parsed ASTs within the same revision are never evicted by Salsa.
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size, lru=200)]
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
let _span = tracing::trace_span!("parsed_module", ?file).entered();
@@ -96,9 +92,14 @@ impl ParsedModule {
self.inner.store(None);
}
/// Returns the file to which this module belongs.
pub fn file(&self) -> File {
self.file
/// Returns the pointer address of this [`ParsedModule`].
///
/// The pointer uniquely identifies the module within the current Salsa revision,
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
pub fn addr(&self) -> usize {
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
// `Arc` within the `ArcSwap` may change.
Arc::as_ptr(&self.inner).addr()
}
}

View File

@@ -667,13 +667,6 @@ impl Deref for SystemPathBuf {
}
}
impl AsRef<Path> for SystemPathBuf {
#[inline]
fn as_ref(&self) -> &Path {
self.0.as_std_path()
}
}
impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
let mut buf = SystemPathBuf::new();

View File

@@ -149,10 +149,6 @@ impl Fix {
&self.edits
}
pub fn into_edits(self) -> Vec<Edit> {
self.edits
}
/// Return the [`Applicability`] of the [`Fix`].
pub fn applicability(&self) -> Applicability {
self.applicability

View File

@@ -49,7 +49,7 @@ impl ModuleImports {
// Resolve the imports.
let mut resolved_imports = ModuleImports::default();
for import in imports {
for resolved in Resolver::new(db, path).resolve(import) {
for resolved in Resolver::new(db).resolve(import) {
if let Some(path) = resolved.as_system_path() {
resolved_imports.insert(path.to_path_buf());
}

View File

@@ -1,9 +1,5 @@
use ruff_db::files::{File, FilePath, system_path_to_file};
use ruff_db::system::SystemPath;
use ty_python_semantic::{
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
resolve_real_module_confident,
};
use ruff_db::files::FilePath;
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
use crate::ModuleDb;
use crate::collector::CollectedImport;
@@ -11,15 +7,12 @@ use crate::collector::CollectedImport;
/// Collect all imports for a given Python file.
pub(crate) struct Resolver<'a> {
db: &'a ModuleDb,
file: Option<File>,
}
impl<'a> Resolver<'a> {
/// Initialize a [`Resolver`] with a given [`ModuleDb`].
pub(crate) fn new(db: &'a ModuleDb, path: &SystemPath) -> Self {
// If we know the importing file we can potentially resolve more imports
let file = system_path_to_file(db, path).ok();
Self { db, file }
pub(crate) fn new(db: &'a ModuleDb) -> Self {
Self { db }
}
/// Resolve the [`CollectedImport`] into a [`FilePath`].
@@ -77,21 +70,13 @@ impl<'a> Resolver<'a> {
/// Resolves a module name to a module.
pub(crate) fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = if let Some(file) = self.file {
resolve_module(self.db, file, module_name)?
} else {
resolve_module_confident(self.db, module_name)?
};
let module = resolve_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
/// Resolves a module name to a module (stubs not allowed).
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = if let Some(file) = self.file {
resolve_real_module(self.db, file, module_name)?
} else {
resolve_real_module_confident(self.db, module_name)?
};
let module = resolve_real_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.14.8"
version = "0.14.6"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -35,7 +35,6 @@ anyhow = { workspace = true }
bitflags = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
compact_str = { workspace = true }
fern = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }

View File

@@ -45,22 +45,3 @@ urllib.request.urlopen(urllib.request.Request(url))
# https://github.com/astral-sh/ruff/issues/15522
map(urllib.request.urlopen, [])
foo = urllib.request.urlopen
# https://github.com/astral-sh/ruff/issues/21462
path = "https://example.com/data.csv"
urllib.request.urlretrieve(path, "data.csv")
url = "https://example.com/api"
urllib.request.Request(url)
# Test resolved f-strings and concatenated string literals
fstring_url = f"https://example.com/data.csv"
urllib.request.urlopen(fstring_url)
urllib.request.Request(fstring_url)
concatenated_url = "https://" + "example.com/data.csv"
urllib.request.urlopen(concatenated_url)
urllib.request.Request(concatenated_url)
nested_concatenated = "http://" + "example.com" + "/data.csv"
urllib.request.urlopen(nested_concatenated)
urllib.request.Request(nested_concatenated)

View File

@@ -52,16 +52,16 @@ def not_broken5():
yield inner()
def broken3():
def not_broken6():
return (yield from [])
def broken4():
def not_broken7():
x = yield from []
return x
def broken5():
def not_broken8():
x = None
def inner(ex):
@@ -76,13 +76,3 @@ class NotBroken9(object):
def __await__(self):
yield from function()
return 42
async def broken6():
yield 1
return foo()
async def broken7():
yield 1
return [1, 2, 3]

View File

@@ -216,15 +216,3 @@ def get_items_list():
def get_items_set():
return tuple({item for item in items}) or None # OK
# https://github.com/astral-sh/ruff/issues/21473
tuple("") or True # SIM222
tuple(t"") or True # OK
tuple(0) or True # OK
tuple(1) or True # OK
tuple(False) or True # OK
tuple(None) or True # OK
tuple(...) or True # OK
tuple(lambda x: x) or True # OK
tuple(x for x in range(0)) or True # OK

View File

@@ -157,15 +157,3 @@ print(f"{1}{''}" and "bar")
# https://github.com/astral-sh/ruff/issues/7127
def f(a: "'' and 'b'"): ...
# https://github.com/astral-sh/ruff/issues/21473
tuple("") and False # SIM223
tuple(t"") and False # OK
tuple(0) and False # OK
tuple(1) and False # OK
tuple(False) and False # OK
tuple(None) and False # OK
tuple(...) and False # OK
tuple(lambda x: x) and False # OK
tuple(x for x in range(0)) and False # OK

View File

@@ -17,24 +17,3 @@ def _():
# Valid yield scope
yield 3
# await is valid in any generator, sync or async
(await cor async for cor in f()) # ok
(await cor for cor in f()) # ok
# but not in comprehensions
[await cor async for cor in f()] # F704
{await cor async for cor in f()} # F704
{await cor: 1 async for cor in f()} # F704
[await cor for cor in f()] # F704
{await cor for cor in f()} # F704
{await cor: 1 for cor in f()} # F704
# or in the iterator of an async generator, which is evaluated in the parent
# scope
(cor async for cor in await f()) # F704
(await cor async for cor in [await c for c in f()]) # F704
# this is also okay because the comprehension is within the generator scope
([await c for c in cor] async for cor in f()) # ok

View File

@@ -1,70 +0,0 @@
import abc
import typing
class User: # Test normal class properties
@property
def name(self): # ERROR: No return
f"{self.first_name} {self.last_name}"
@property
def age(self): # OK: Returning something
return 100
def method(self): # OK: Not a property
x = 1
@property
def nested(self): # ERROR: Property itself doesn't return
def inner():
return 0
@property
def stub(self): ... # OK: A stub; doesn't return anything
class UserMeta(metaclass=abc.ABCMeta): # Test properies inside of an ABC class
@property
@abc.abstractmethod
def abstr_prop1(self): ... # OK: Abstract methods doesn't need to return anything
@property
@abc.abstractmethod
def abstr_prop2(self): # OK: Abstract methods doesn't need to return anything
"""
A cool docstring
"""
@property
def prop1(self): # OK: Returning a value
return 1
@property
def prop2(self): # ERROR: Not returning something (even when we are inside an ABC)
50
def method(self): # OK: Not a property
x = 1
def func(): # OK: Not a property
x = 1
class Proto(typing.Protocol): # Tests for a Protocol class
@property
def prop1(self) -> int: ... # OK: A stub property
class File: # Extra tests for things like yield/yield from/raise
@property
def stream1(self): # OK: Yields something
yield
@property
def stream2(self): # OK: Yields from something
yield from self.stream1
@property
def children(self): # OK: Raises
raise ValueError("File does not have children")

View File

@@ -3,5 +3,3 @@ def func():
# Top-level await
await 1
([await c for c in cor] async for cor in func()) # ok

View File

@@ -1,24 +0,0 @@
async def gen():
yield 1
return 42
def gen(): # B901 but not a syntax error - not an async generator
yield 1
return 42
async def gen(): # ok - no value in return
yield 1
return
async def gen():
yield 1
return foo()
async def gen():
yield 1
return [1, 2, 3]
async def gen():
if True:
yield 1
return 10

View File

@@ -17,7 +17,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:8:5: F841 [
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff_linter/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the `--fix` option.
[*] 7 potentially fixable with the --fix option.
```
Running from the project directory itself should exhibit the same behavior:
@@ -32,7 +32,7 @@ examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but n
project/file.py:1:8: F401 [*] `os` imported but unused
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the `--fix` option.
[*] 7 potentially fixable with the --fix option.
```
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
@@ -43,7 +43,7 @@ files:
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
Found 2 errors.
[*] 2 potentially fixable with the `--fix` option.
[*] 2 potentially fixable with the --fix option.
```
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
@@ -61,7 +61,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:4:27: F401
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
Found 9 errors.
[*] 9 potentially fixable with the `--fix` option.
[*] 9 potentially fixable with the --fix option.
```
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
@@ -74,7 +74,7 @@ docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
Found 4 errors.
[*] 4 potentially fixable with the `--fix` option.
[*] 4 potentially fixable with the --fix option.
```
Passing an excluded directory directly should report errors in the contained files:
@@ -83,7 +83,7 @@ Passing an excluded directory directly should report errors in the contained fil
∴ cargo run -p ruff -- check crates/ruff_linter/resources/test/project/examples/excluded/
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 potentially fixable with the `--fix` option.
[*] 1 potentially fixable with the --fix option.
```
Unless we `--force-exclude`:

View File

@@ -347,9 +347,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.is_rule_enabled(Rule::InvalidArgumentName) {
pep8_naming::rules::invalid_argument_name_function(checker, function_def);
}
if checker.is_rule_enabled(Rule::PropertyWithoutReturn) {
ruff::rules::property_without_return(checker, function_def);
}
}
Stmt::Return(_) => {
if checker.is_rule_enabled(Rule::ReturnInInit) {

View File

@@ -35,7 +35,6 @@ use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::str::Quote;
use ruff_python_ast::token::Tokens;
use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern};
use ruff_python_ast::{
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
@@ -49,7 +48,7 @@ use ruff_python_parser::semantic_errors::{
SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind,
};
use ruff_python_parser::typing::{AnnotationKind, ParsedAnnotation, parse_type_annotation};
use ruff_python_parser::{ParseError, Parsed};
use ruff_python_parser::{ParseError, Parsed, Tokens};
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
use ruff_python_semantic::analyze::{imports, typing};
use ruff_python_semantic::{
@@ -69,7 +68,6 @@ use crate::noqa::NoqaMapping;
use crate::package::PackageRoot;
use crate::preview::is_undefined_export_in_dunder_init_enabled;
use crate::registry::Rule;
use crate::rules::flake8_bugbear::rules::ReturnInGenerator;
use crate::rules::pyflakes::rules::{
LateFutureImport, MultipleStarredExpressions, ReturnOutsideFunction,
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
@@ -730,12 +728,6 @@ impl SemanticSyntaxContext for Checker<'_> {
self.report_diagnostic(NonlocalWithoutBinding { name }, error.range);
}
}
SemanticSyntaxErrorKind::ReturnInGenerator => {
// B901
if self.is_rule_enabled(Rule::ReturnInGenerator) {
self.report_diagnostic(ReturnInGenerator, error.range);
}
}
SemanticSyntaxErrorKind::ReboundComprehensionVariable
| SemanticSyntaxErrorKind::DuplicateTypeParameter
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
@@ -754,7 +746,6 @@ impl SemanticSyntaxContext for Checker<'_> {
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
self.semantic_errors.borrow_mut().push(error);
}
@@ -788,10 +779,6 @@ impl SemanticSyntaxContext for Checker<'_> {
match scope.kind {
ScopeKind::Class(_) => return false,
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
ScopeKind::Generator {
kind: GeneratorKind::Generator,
..
} => return true,
ScopeKind::Generator { .. }
| ScopeKind::Module
| ScopeKind::Type
@@ -841,19 +828,14 @@ impl SemanticSyntaxContext for Checker<'_> {
self.source_type.is_ipynb()
}
fn in_generator_context(&self) -> bool {
for scope in self.semantic.current_scopes() {
if matches!(
scope.kind,
ScopeKind::Generator {
kind: GeneratorKind::Generator,
..
}
) {
return true;
fn in_generator_scope(&self) -> bool {
matches!(
&self.semantic.current_scope().kind,
ScopeKind::Generator {
kind: GeneratorKind::Generator,
..
}
}
false
)
}
fn in_loop_context(&self) -> bool {

View File

@@ -1,6 +1,6 @@
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::{TokenKind, Tokens};
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};

View File

@@ -4,9 +4,9 @@ use std::path::Path;
use ruff_notebook::CellOffsets;
use ruff_python_ast::PySourceType;
use ruff_python_ast::token::Tokens;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::Tokens;
use crate::Locator;
use crate::directives::TodoComment;

View File

@@ -1058,7 +1058,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "063") => rules::ruff::rules::AccessAnnotationsFromClassDict,
(Ruff, "064") => rules::ruff::rules::NonOctalPermissions,
(Ruff, "065") => rules::ruff::rules::LoggingEagerConversion,
(Ruff, "066") => rules::ruff::rules::PropertyWithoutReturn,
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,

View File

@@ -5,8 +5,8 @@ use std::str::FromStr;
use bitflags::bitflags;
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_index::Indexer;
use ruff_python_parser::{TokenKind, Tokens};
use ruff_python_trivia::CommentRanges;
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};

View File

@@ -5,8 +5,8 @@ use std::iter::FusedIterator;
use std::slice::Iter;
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
use ruff_python_ast::token::{Token, TokenKind, Tokens};
use ruff_python_ast::{self as ast, Stmt, Suite};
use ruff_python_parser::{Token, TokenKind, Tokens};
use ruff_source_file::UniversalNewlineIterator;
use ruff_text_size::{Ranged, TextSize};

View File

@@ -9,11 +9,10 @@ use anyhow::Result;
use libcst_native as cst;
use ruff_diagnostics::Edit;
use ruff_python_ast::token::Tokens;
use ruff_python_ast::{self as ast, Expr, ModModule, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_importer::Insertion;
use ruff_python_parser::Parsed;
use ruff_python_parser::{Parsed, Tokens};
use ruff_python_semantic::{
ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel,
};

View File

@@ -46,7 +46,6 @@ pub mod rule_selector;
pub mod rules;
pub mod settings;
pub mod source_kind;
pub mod suppression;
mod text_helpers;
pub mod upstream_categories;
mod violation;

View File

@@ -1043,7 +1043,6 @@ mod tests {
Rule::YieldFromInAsyncFunction,
Path::new("yield_from_in_async_function.py")
)]
#[test_case(Rule::ReturnInGenerator, Path::new("return_in_generator.py"))]
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().to_string();
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);

View File

@@ -279,10 +279,3 @@ pub(crate) const fn is_extended_snmp_api_path_detection_enabled(settings: &Linte
pub(crate) const fn is_enumerate_for_loop_int_index_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/21469
pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

View File

@@ -10,11 +10,11 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::assert_diagnostics;
use crate::registry::Rule;
use crate::settings::LinterSettings;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_diagnostics, assert_diagnostics_diff};
#[test_case(Rule::Assert, Path::new("S101.py"))]
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
@@ -112,19 +112,14 @@ mod tests {
rule_code.noqa_code(),
path.to_string_lossy()
);
assert_diagnostics_diff!(
snapshot,
let diagnostics = test_path(
Path::new("flake8_bandit").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Disabled,
..LinterSettings::for_rule(rule_code)
},
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
}
);
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}

View File

@@ -4,16 +4,11 @@
use itertools::Either;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::typing::find_binding_value;
use ruff_text_size::{Ranged, TextRange};
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::preview::{
is_s310_resolve_string_literal_bindings_enabled, is_suspicious_function_reference_enabled,
};
use crate::settings::LinterSettings;
use crate::preview::is_suspicious_function_reference_enabled;
/// ## What it does
/// Checks for calls to `pickle` functions or modules that wrap them.
@@ -1021,25 +1016,6 @@ fn suspicious_function(
|| has_prefix(chars.skip_while(|c| c.is_whitespace()), "https://")
}
/// Resolves `expr` to its binding and checks if the resolved expression starts with an HTTP or HTTPS prefix.
fn expression_starts_with_http_prefix(
expr: &Expr,
semantic: &SemanticModel,
settings: &LinterSettings,
) -> bool {
let resolved_expression = if is_s310_resolve_string_literal_bindings_enabled(settings)
&& let Some(name_expr) = expr.as_name_expr()
&& let Some(binding_id) = semantic.only_binding(name_expr)
&& let Some(value) = find_binding_value(semantic.binding(binding_id), semantic)
{
value
} else {
expr
};
leading_chars(resolved_expression).is_some_and(has_http_prefix)
}
/// Return the leading characters for an expression, if it's a string literal, f-string, or
/// string concatenation.
fn leading_chars(expr: &Expr) -> Option<impl Iterator<Item = char> + Clone + '_> {
@@ -1163,19 +1139,17 @@ fn suspicious_function(
// URLOpen (`Request`)
["urllib", "request", "Request"] | ["six", "moves", "urllib", "request", "Request"] => {
if let Some(arguments) = arguments {
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
// If the `url` argument is a string literal or an f-string, allow `http` and `https` schemes.
if arguments.args.iter().all(|arg| !arg.is_starred_expr())
&& arguments
.keywords
.iter()
.all(|keyword| keyword.arg.is_some())
{
if let Some(url_expr) = arguments.find_argument_value("url", 0)
&& expression_starts_with_http_prefix(
url_expr,
checker.semantic(),
checker.settings(),
)
if arguments
.find_argument_value("url", 0)
.and_then(leading_chars)
.is_some_and(has_http_prefix)
{
return;
}
@@ -1212,25 +1186,19 @@ fn suspicious_function(
name.segments() == ["urllib", "request", "Request"]
})
{
if let Some(url_expr) = arguments.find_argument_value("url", 0)
&& expression_starts_with_http_prefix(
url_expr,
checker.semantic(),
checker.settings(),
)
if arguments
.find_argument_value("url", 0)
.and_then(leading_chars)
.is_some_and(has_http_prefix)
{
return;
}
}
}
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
// If the `url` argument is a string literal, allow `http` and `https` schemes.
Some(expr) => {
if expression_starts_with_http_prefix(
expr,
checker.semantic(),
checker.settings(),
) {
if leading_chars(expr).is_some_and(has_http_prefix) {
return;
}
}

View File

@@ -254,84 +254,3 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
42 | urllib.request.urlopen(urllib.request.Request(url))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:51:1
|
49 | # https://github.com/astral-sh/ruff/issues/21462
50 | path = "https://example.com/data.csv"
51 | urllib.request.urlretrieve(path, "data.csv")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | url = "https://example.com/api"
53 | urllib.request.Request(url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:53:1
|
51 | urllib.request.urlretrieve(path, "data.csv")
52 | url = "https://example.com/api"
53 | urllib.request.Request(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
54 |
55 | # Test resolved f-strings and concatenated string literals
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:57:1
|
55 | # Test resolved f-strings and concatenated string literals
56 | fstring_url = f"https://example.com/data.csv"
57 | urllib.request.urlopen(fstring_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 | urllib.request.Request(fstring_url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:58:1
|
56 | fstring_url = f"https://example.com/data.csv"
57 | urllib.request.urlopen(fstring_url)
58 | urllib.request.Request(fstring_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59 |
60 | concatenated_url = "https://" + "example.com/data.csv"
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:61:1
|
60 | concatenated_url = "https://" + "example.com/data.csv"
61 | urllib.request.urlopen(concatenated_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 | urllib.request.Request(concatenated_url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:62:1
|
60 | concatenated_url = "https://" + "example.com/data.csv"
61 | urllib.request.urlopen(concatenated_url)
62 | urllib.request.Request(concatenated_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63 |
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:65:1
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
65 | urllib.request.urlopen(nested_concatenated)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 | urllib.request.Request(nested_concatenated)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:66:1
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
65 | urllib.request.urlopen(nested_concatenated)
66 | urllib.request.Request(nested_concatenated)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|

View File

@@ -1,15 +1,15 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
--> S301.py:3:1
|
1 | import pickle
2 |
3 | pickle.loads()
| ^^^^^^^^^^^^^^
|
--- Summary ---
Removed: 0
Added: 2
--- Added ---
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
--> S301.py:7:5
|
@@ -19,7 +19,6 @@ S301 `pickle` and modules that wrap it can be unsafe when used to deserialize un
8 | foo = pickle.load
|
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
--> S301.py:8:7
|

View File

@@ -1,15 +1,24 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S307 Use of possibly insecure function; consider using `ast.literal_eval`
--> S307.py:3:7
|
1 | import os
2 |
3 | print(eval("1+1")) # S307
| ^^^^^^^^^^^
4 | print(eval("os.getcwd()")) # S307
|
--- Summary ---
Removed: 0
Added: 2
S307 Use of possibly insecure function; consider using `ast.literal_eval`
--> S307.py:4:7
|
3 | print(eval("1+1")) # S307
4 | print(eval("os.getcwd()")) # S307
| ^^^^^^^^^^^^^^^^^^^
|
--- Added ---
S307 Use of possibly insecure function; consider using `ast.literal_eval`
--> S307.py:16:5
|
@@ -19,7 +28,6 @@ S307 Use of possibly insecure function; consider using `ast.literal_eval`
17 | foo = eval
|
S307 Use of possibly insecure function; consider using `ast.literal_eval`
--> S307.py:17:7
|

View File

@@ -1,37 +1,60 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 2
Added: 4
--- Removed ---
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:16:1
|
16 | @mark_safe
| ^^^^^^^^^^
17 | def some_func():
18 | return '<script>alert("evil!")</script>'
|
--> S308.py:6:5
|
4 | def bad_func():
5 | inject = "harmful_input"
6 | mark_safe(inject)
| ^^^^^^^^^^^^^^^^^
7 | mark_safe("I will add" + inject + "to my string")
8 | mark_safe("I will add %s to my string" % inject)
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:36:1
--> S308.py:7:5
|
5 | inject = "harmful_input"
6 | mark_safe(inject)
7 | mark_safe("I will add" + inject + "to my string")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | mark_safe("I will add %s to my string" % inject)
9 | mark_safe("I will add {} to my string".format(inject))
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:8:5
|
36 | @mark_safe
| ^^^^^^^^^^
37 | def some_func():
38 | return '<script>alert("evil!")</script>'
6 | mark_safe(inject)
7 | mark_safe("I will add" + inject + "to my string")
8 | mark_safe("I will add %s to my string" % inject)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9 | mark_safe("I will add {} to my string".format(inject))
10 | mark_safe(f"I will add {inject} to my string")
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:9:5
|
7 | mark_safe("I will add" + inject + "to my string")
8 | mark_safe("I will add %s to my string" % inject)
9 | mark_safe("I will add {} to my string".format(inject))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 | mark_safe(f"I will add {inject} to my string")
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:10:5
|
8 | mark_safe("I will add %s to my string" % inject)
9 | mark_safe("I will add {} to my string".format(inject))
10 | mark_safe(f"I will add {inject} to my string")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 |
12 | def good_func():
|
--- Added ---
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:16:2
|
@@ -41,6 +64,59 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
18 | return '<script>alert("evil!")</script>'
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:26:5
|
24 | def bad_func():
25 | inject = "harmful_input"
26 | mark_safe(inject)
| ^^^^^^^^^^^^^^^^^
27 | mark_safe("I will add" + inject + "to my string")
28 | mark_safe("I will add %s to my string" % inject)
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:27:5
|
25 | inject = "harmful_input"
26 | mark_safe(inject)
27 | mark_safe("I will add" + inject + "to my string")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | mark_safe("I will add %s to my string" % inject)
29 | mark_safe("I will add {} to my string".format(inject))
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:28:5
|
26 | mark_safe(inject)
27 | mark_safe("I will add" + inject + "to my string")
28 | mark_safe("I will add %s to my string" % inject)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 | mark_safe("I will add {} to my string".format(inject))
30 | mark_safe(f"I will add {inject} to my string")
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:29:5
|
27 | mark_safe("I will add" + inject + "to my string")
28 | mark_safe("I will add %s to my string" % inject)
29 | mark_safe("I will add {} to my string".format(inject))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 | mark_safe(f"I will add {inject} to my string")
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:30:5
|
28 | mark_safe("I will add %s to my string" % inject)
29 | mark_safe("I will add {} to my string".format(inject))
30 | mark_safe(f"I will add {inject} to my string")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 |
32 | def good_func():
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:36:2
@@ -51,7 +127,6 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
38 | return '<script>alert("evil!")</script>'
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:42:5
|
@@ -61,7 +136,6 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
43 | foo = mark_safe
|
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
--> S308.py:43:7
|

View File

@@ -1,106 +1,260 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 8
Added: 2
--- Removed ---
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:51:1
|
49 | # https://github.com/astral-sh/ruff/issues/21462
50 | path = "https://example.com/data.csv"
51 | urllib.request.urlretrieve(path, "data.csv")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | url = "https://example.com/api"
53 | urllib.request.Request(url)
|
--> S310.py:6:1
|
4 | urllib.request.urlopen(url=f'http://www.google.com')
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
8 | urllib.request.urlopen('http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:53:1
--> S310.py:7:1
|
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | urllib.request.urlopen('http://www.google.com')
9 | urllib.request.urlopen(f'http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:10:1
|
51 | urllib.request.urlretrieve(path, "data.csv")
52 | url = "https://example.com/api"
53 | urllib.request.Request(url)
8 | urllib.request.urlopen('http://www.google.com')
9 | urllib.request.urlopen(f'http://www.google.com')
10 | urllib.request.urlopen('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 | urllib.request.urlopen(url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:11:1
|
9 | urllib.request.urlopen(f'http://www.google.com')
10 | urllib.request.urlopen('file:///foo/bar/baz')
11 | urllib.request.urlopen(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
54 |
55 | # Test resolved f-strings and concatenated string literals
12 |
13 | urllib.request.Request(url='http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:57:1
--> S310.py:16:1
|
55 | # Test resolved f-strings and concatenated string literals
56 | fstring_url = f"https://example.com/data.csv"
57 | urllib.request.urlopen(fstring_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 | urllib.request.Request(fstring_url)
14 | urllib.request.Request(url=f'http://www.google.com')
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
18 | urllib.request.Request('http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:58:1
--> S310.py:17:1
|
56 | fstring_url = f"https://example.com/data.csv"
57 | urllib.request.urlopen(fstring_url)
58 | urllib.request.Request(fstring_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59 |
60 | concatenated_url = "https://" + "example.com/data.csv"
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18 | urllib.request.Request('http://www.google.com')
19 | urllib.request.Request(f'http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:61:1
--> S310.py:20:1
|
60 | concatenated_url = "https://" + "example.com/data.csv"
61 | urllib.request.urlopen(concatenated_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 | urllib.request.Request(concatenated_url)
18 | urllib.request.Request('http://www.google.com')
19 | urllib.request.Request(f'http://www.google.com')
20 | urllib.request.Request('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
21 | urllib.request.Request(url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:62:1
--> S310.py:21:1
|
60 | concatenated_url = "https://" + "example.com/data.csv"
61 | urllib.request.urlopen(concatenated_url)
62 | urllib.request.Request(concatenated_url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63 |
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
19 | urllib.request.Request(f'http://www.google.com')
20 | urllib.request.Request('file:///foo/bar/baz')
21 | urllib.request.Request(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
22 |
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:65:1
--> S310.py:23:1
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
65 | urllib.request.urlopen(nested_concatenated)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 | urllib.request.Request(nested_concatenated)
21 | urllib.request.Request(url)
22 |
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:66:1
--> S310.py:24:1
|
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
65 | urllib.request.urlopen(nested_concatenated)
66 | urllib.request.Request(nested_concatenated)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:25:1
|
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:26:1
|
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
28 | urllib.request.URLopener().open('http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:27:1
|
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | urllib.request.URLopener().open('http://www.google.com')
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:28:1
|
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
28 | urllib.request.URLopener().open('http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
29 | urllib.request.URLopener().open(f'http://www.google.com')
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:29:1
|
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
28 | urllib.request.URLopener().open('http://www.google.com')
29 | urllib.request.URLopener().open(f'http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:30:1
|
28 | urllib.request.URLopener().open('http://www.google.com')
29 | urllib.request.URLopener().open(f'http://www.google.com')
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
32 | urllib.request.URLopener().open(url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:31:1
|
29 | urllib.request.URLopener().open(f'http://www.google.com')
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
32 | urllib.request.URLopener().open(url)
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:32:1
|
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
32 | urllib.request.URLopener().open(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
33 |
34 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:37:1
|
35 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'))
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:38:1
|
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:41:1
|
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 | urllib.request.urlopen(urllib.request.Request(url))
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:41:24
|
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 | urllib.request.urlopen(urllib.request.Request(url))
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:42:1
|
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
42 | urllib.request.urlopen(urllib.request.Request(url))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:42:24
|
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
42 | urllib.request.urlopen(urllib.request.Request(url))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
--- Added ---
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:46:5
|
@@ -110,7 +264,6 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
47 | foo = urllib.request.urlopen
|
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
--> S310.py:47:7
|
@@ -118,6 +271,4 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
46 | map(urllib.request.urlopen, [])
47 | foo = urllib.request.urlopen
| ^^^^^^^^^^^^^^^^^^^^^^
48 |
49 | # https://github.com/astral-sh/ruff/issues/21462
|

View File

@@ -1,15 +1,103 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:10:1
|
9 | # Errors
10 | random.Random()
| ^^^^^^^^^^^^^^^
11 | random.random()
12 | random.randrange()
|
--- Summary ---
Removed: 0
Added: 2
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:11:1
|
9 | # Errors
10 | random.Random()
11 | random.random()
| ^^^^^^^^^^^^^^^
12 | random.randrange()
13 | random.randint()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:12:1
|
10 | random.Random()
11 | random.random()
12 | random.randrange()
| ^^^^^^^^^^^^^^^^^^
13 | random.randint()
14 | random.choice()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:13:1
|
11 | random.random()
12 | random.randrange()
13 | random.randint()
| ^^^^^^^^^^^^^^^^
14 | random.choice()
15 | random.choices()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:14:1
|
12 | random.randrange()
13 | random.randint()
14 | random.choice()
| ^^^^^^^^^^^^^^^
15 | random.choices()
16 | random.uniform()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:15:1
|
13 | random.randint()
14 | random.choice()
15 | random.choices()
| ^^^^^^^^^^^^^^^^
16 | random.uniform()
17 | random.triangular()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:16:1
|
14 | random.choice()
15 | random.choices()
16 | random.uniform()
| ^^^^^^^^^^^^^^^^
17 | random.triangular()
18 | random.randbytes()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:17:1
|
15 | random.choices()
16 | random.uniform()
17 | random.triangular()
| ^^^^^^^^^^^^^^^^^^^
18 | random.randbytes()
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:18:1
|
16 | random.uniform()
17 | random.triangular()
18 | random.randbytes()
| ^^^^^^^^^^^^^^^^^^
19 |
20 | # Unrelated
|
--- Added ---
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:26:5
|
@@ -19,7 +107,6 @@ S311 Standard pseudo-random generators are not suitable for cryptographic purpos
27 | foo = random.randrange
|
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
--> S311.py:27:7
|

View File

@@ -1,15 +1,15 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
--> S312.py:3:1
|
1 | from telnetlib import Telnet
2 |
3 | Telnet("localhost", 23)
| ^^^^^^^^^^^^^^^^^^^^^^^
|
--- Summary ---
Removed: 0
Added: 3
--- Added ---
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
--> S312.py:7:5
|
@@ -19,7 +19,6 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
8 | foo = Telnet
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
--> S312.py:8:7
|
@@ -31,7 +30,6 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
10 | import telnetlib
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
--> S312.py:11:5
|
@@ -41,3 +39,13 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
12 |
13 | from typing import Annotated
|
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
--> S312.py:14:24
|
13 | from typing import Annotated
14 | foo: Annotated[Telnet, telnetlib.Telnet()]
| ^^^^^^^^^^^^^^^^^^
15 |
16 | def _() -> Telnet: ...
|

View File

@@ -1,15 +1,26 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:3:25
|
1 | from pysnmp.hlapi import CommunityData
2 |
3 | CommunityData("public", mpModel=0) # S508
| ^^^^^^^^^
4 | CommunityData("public", mpModel=1) # S508
|
--- Summary ---
Removed: 0
Added: 8
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:4:25
|
3 | CommunityData("public", mpModel=0) # S508
4 | CommunityData("public", mpModel=1) # S508
| ^^^^^^^^^
5 |
6 | CommunityData("public", mpModel=2) # OK
|
--- Added ---
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:18:46
|
@@ -21,7 +32,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:19:58
|
@@ -32,7 +42,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:20:53
|
@@ -44,7 +53,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:21:45
|
@@ -56,7 +64,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:22:58
|
@@ -68,7 +75,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:23:53
|
@@ -80,7 +86,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:24:45
|
@@ -91,7 +96,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
--> S508.py:25:43
|

View File

@@ -1,15 +1,24 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:4:12
|
4 | insecure = UsmUserData("securityName") # S509
| ^^^^^^^^^^^
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
|
--- Summary ---
Removed: 0
Added: 4
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:5:16
|
4 | insecure = UsmUserData("securityName") # S509
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
| ^^^^^^^^^^^
6 |
7 | less_insecure = UsmUserData("securityName", "authName", "privName") # OK
|
--- Added ---
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:15:1
|
@@ -21,7 +30,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:16:1
|
@@ -32,7 +40,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:17:1
|
@@ -43,7 +50,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
--> S509.py:18:1
|

View File

@@ -25,11 +25,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// keyword-only argument, to force callers to be explicit when providing
/// the argument.
///
/// This rule exempts methods decorated with [`@typing.override`][override],
/// since changing the signature of a subclass method that overrides a
/// superclass method may cause type checkers to complain about a violation of
/// the Liskov Substitution Principle.
///
/// ## Example
/// ```python
/// from math import ceil, floor
@@ -94,8 +89,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// ## References
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.127")]
pub(crate) struct BooleanDefaultValuePositionalArgument;

View File

@@ -28,7 +28,7 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// the argument.
///
/// Dunder methods that define operators are exempt from this rule, as are
/// setters and [`@override`][override] definitions.
/// setters and `@override` definitions.
///
/// ## Example
///
@@ -93,8 +93,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// ## References
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.127")]
pub(crate) struct BooleanTypeHintPositionalArgument;

View File

@@ -1,5 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
use ruff_python_ast::statement_visitor;
use ruff_python_ast::statement_visitor::StatementVisitor;
use ruff_python_ast::{self as ast, Expr, Stmt, StmtFunctionDef};
use ruff_text_size::TextRange;
@@ -95,11 +96,6 @@ pub(crate) fn return_in_generator(checker: &Checker, function_def: &StmtFunction
return;
}
// Async functions are flagged by the `ReturnInGenerator` semantic syntax error.
if function_def.is_async {
return;
}
let mut visitor = ReturnInGeneratorVisitor::default();
visitor.visit_body(&function_def.body);
@@ -116,9 +112,15 @@ struct ReturnInGeneratorVisitor {
has_yield: bool,
}
impl Visitor<'_> for ReturnInGeneratorVisitor {
impl StatementVisitor<'_> for ReturnInGeneratorVisitor {
fn visit_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Expr(ast::StmtExpr { value, .. }) => match **value {
Expr::Yield(_) | Expr::YieldFrom(_) => {
self.has_yield = true;
}
_ => {}
},
Stmt::FunctionDef(_) => {
// Do not recurse into nested functions; they're evaluated separately.
}
@@ -128,19 +130,8 @@ impl Visitor<'_> for ReturnInGeneratorVisitor {
node_index: _,
}) => {
self.return_ = Some(*range);
walk_stmt(self, stmt);
}
_ => walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &Expr) {
match expr {
Expr::Lambda(_) => {}
Expr::Yield(_) | Expr::YieldFrom(_) => {
self.has_yield = true;
}
_ => walk_expr(self, expr),
_ => statement_visitor::walk_stmt(self, stmt),
}
}
}

View File

@@ -21,46 +21,3 @@ B901 Using `yield` and `return {value}` in a generator function can lead to conf
37 |
38 | yield from not_broken()
|
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
--> B901.py:56:5
|
55 | def broken3():
56 | return (yield from [])
| ^^^^^^^^^^^^^^^^^^^^^^
|
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
--> B901.py:61:5
|
59 | def broken4():
60 | x = yield from []
61 | return x
| ^^^^^^^^
|
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
--> B901.py:72:5
|
71 | inner((yield from []))
72 | return x
| ^^^^^^^^
|
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
--> B901.py:83:5
|
81 | async def broken6():
82 | yield 1
83 | return foo()
| ^^^^^^^^^^^^
|
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
--> B901.py:88:5
|
86 | async def broken7():
87 | yield 1
88 | return [1, 2, 3]
| ^^^^^^^^^^^^^^^^
|

View File

@@ -17,8 +17,6 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// non-obvious errors, as readers may mistake the argument for the
/// builtin and vice versa.
///
/// Function definitions decorated with [`@override`][override] or
/// [`@overload`][overload] are exempt from this rule by default.
/// Builtins can be marked as exceptions to this rule via the
/// [`lint.flake8-builtins.ignorelist`] configuration option.
///
@@ -50,9 +48,6 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ## References
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
/// [overload]: https://docs.python.org/3/library/typing.html#typing.overload
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.48")]
pub(crate) struct BuiltinArgumentShadowing {

View File

@@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_index::Indexer;
use ruff_python_parser::{TokenKind, Tokens};
use ruff_text_size::{Ranged, TextRange};
use crate::Locator;

View File

@@ -3,7 +3,7 @@ use ruff_python_ast as ast;
use ruff_python_ast::ExprGenerator;
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::TokenKind;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;

View File

@@ -3,7 +3,7 @@ use ruff_python_ast as ast;
use ruff_python_ast::ExprGenerator;
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::TokenKind;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;

View File

@@ -1,7 +1,7 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::TokenKind;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;

View File

@@ -3,8 +3,8 @@ use std::borrow::Cow;
use itertools::Itertools;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::StringFlags;
use ruff_python_ast::token::{Token, TokenKind, Tokens};
use ruff_python_index::Indexer;
use ruff_python_parser::{Token, TokenKind, Tokens};
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextLen, TextRange};

View File

@@ -1,6 +1,8 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::whitespace::trailing_comment_start_offset;
use ruff_python_ast::{Expr, ExprStringLiteral, Stmt, StmtExpr};
use ruff_python_semantic::{ScopeKind, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -99,7 +101,7 @@ pub(crate) fn unnecessary_placeholder(checker: &Checker, body: &[Stmt]) {
// Ellipses are significant in protocol methods and abstract methods.
// Specifically, Pyright uses the presence of an ellipsis to indicate that
// a method is a stub, rather than a default implementation.
if checker.semantic().in_protocol_or_abstract_method() {
if in_protocol_or_abstract_method(checker.semantic()) {
return;
}
Placeholder::Ellipsis
@@ -161,3 +163,21 @@ impl std::fmt::Display for Placeholder {
}
}
}
/// Return `true` if the [`SemanticModel`] is in a `typing.Protocol` subclass or an abstract
/// method.
fn in_protocol_or_abstract_method(semantic: &SemanticModel) -> bool {
semantic.current_scopes().any(|scope| match scope.kind {
ScopeKind::Class(class_def) => class_def
.bases()
.iter()
.any(|base| semantic.match_typing_expr(map_subscript(base), "Protocol")),
ScopeKind::Function(function_def) => {
ruff_python_semantic::analyze::visibility::is_abstract(
&function_def.decorator_list,
semantic,
)
}
_ => false,
})
}

View File

@@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_parser::{TokenKind, Tokens};
use ruff_text_size::{Ranged, TextLen, TextSize};
use crate::checkers::ast::Checker;

View File

@@ -4,10 +4,10 @@ use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::helpers::{is_const_false, is_const_true};
use ruff_python_ast::stmt_if::elif_else_range;
use ruff_python_ast::token::TokenKind;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::whitespace::indentation;
use ruff_python_ast::{self as ast, Decorator, ElifElseClause, Expr, Stmt};
use ruff_python_parser::TokenKind;
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::visibility::is_property;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer, is_python_whitespace};

View File

@@ -1144,23 +1144,3 @@ help: Replace with `(i for i in range(1))`
208 | # https://github.com/astral-sh/ruff/issues/21136
209 | def get_items():
note: This is an unsafe fix and may change runtime behavior
SIM222 [*] Use `True` instead of `... or True`
--> SIM222.py:222:1
|
221 | # https://github.com/astral-sh/ruff/issues/21473
222 | tuple("") or True # SIM222
| ^^^^^^^^^^^^^^^^^
223 | tuple(t"") or True # OK
224 | tuple(0) or True # OK
|
help: Replace with `True`
219 |
220 |
221 | # https://github.com/astral-sh/ruff/issues/21473
- tuple("") or True # SIM222
222 + True # SIM222
223 | tuple(t"") or True # OK
224 | tuple(0) or True # OK
225 | tuple(1) or True # OK
note: This is an unsafe fix and may change runtime behavior

View File

@@ -1025,23 +1025,3 @@ help: Replace with `f"{''}{''}"`
156 |
157 |
note: This is an unsafe fix and may change runtime behavior
SIM223 [*] Use `tuple("")` instead of `tuple("") and ...`
--> SIM223.py:163:1
|
162 | # https://github.com/astral-sh/ruff/issues/21473
163 | tuple("") and False # SIM223
| ^^^^^^^^^^^^^^^^^^^
164 | tuple(t"") and False # OK
165 | tuple(0) and False # OK
|
help: Replace with `tuple("")`
160 |
161 |
162 | # https://github.com/astral-sh/ruff/issues/21473
- tuple("") and False # SIM223
163 + tuple("") # SIM223
164 | tuple(t"") and False # OK
165 | tuple(0) and False # OK
166 | tuple(1) and False # OK
note: This is an unsafe fix and may change runtime behavior

View File

@@ -60,16 +60,6 @@ impl Violation for UnusedFunctionArgument {
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// This rule exempts methods decorated with [`@typing.override`][override].
/// Removing a parameter from a subclass method (or changing a parameter's
/// name) may cause type checkers to complain about a violation of the Liskov
/// Substitution Principle if it means that the method now incompatibly
/// overrides a method defined on a superclass. Explicitly decorating an
/// overriding method with `@override` signals to Ruff that the method is
/// intended to override a superclass method and that a type checker will
/// enforce that it does so; Ruff therefore knows that it should not enforce
/// rules about unused arguments on such methods.
///
/// ## Example
/// ```python
/// class Class:
@@ -86,8 +76,6 @@ impl Violation for UnusedFunctionArgument {
///
/// ## Options
/// - `lint.dummy-variable-rgx`
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.168")]
pub(crate) struct UnusedMethodArgument {
@@ -113,16 +101,6 @@ impl Violation for UnusedMethodArgument {
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// This rule exempts methods decorated with [`@typing.override`][override].
/// Removing a parameter from a subclass method (or changing a parameter's
/// name) may cause type checkers to complain about a violation of the Liskov
/// Substitution Principle if it means that the method now incompatibly
/// overrides a method defined on a superclass. Explicitly decorating an
/// overriding method with `@override` signals to Ruff that the method is
/// intended to override a superclass method and that a type checker will
/// enforce that it does so; Ruff therefore knows that it should not enforce
/// rules about unused arguments on such methods.
///
/// ## Example
/// ```python
/// class Class:
@@ -141,8 +119,6 @@ impl Violation for UnusedMethodArgument {
///
/// ## Options
/// - `lint.dummy-variable-rgx`
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.168")]
pub(crate) struct UnusedClassMethodArgument {
@@ -168,16 +144,6 @@ impl Violation for UnusedClassMethodArgument {
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// This rule exempts methods decorated with [`@typing.override`][override].
/// Removing a parameter from a subclass method (or changing a parameter's
/// name) may cause type checkers to complain about a violation of the Liskov
/// Substitution Principle if it means that the method now incompatibly
/// overrides a method defined on a superclass. Explicitly decorating an
/// overriding method with `@override` signals to Ruff that the method is
/// intended to override a superclass method, and that a type checker will
/// enforce that it does so; Ruff therefore knows that it should not enforce
/// rules about unused arguments on such methods.
///
/// ## Example
/// ```python
/// class Class:
@@ -196,8 +162,6 @@ impl Violation for UnusedClassMethodArgument {
///
/// ## Options
/// - `lint.dummy-variable-rgx`
///
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.168")]
pub(crate) struct UnusedStaticMethodArgument {

View File

@@ -57,7 +57,7 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
fn_argument: &str,
fix_enabled: bool,
violation: impl Violation,
applicability: Applicability,
applicability: Option<Applicability>,
) {
if call.arguments.len() != 1 {
return;
@@ -91,14 +91,18 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
let edit = Edit::range_replacement(replacement, range);
let applicability = match applicability {
Applicability::DisplayOnly => Applicability::DisplayOnly,
_ if checker.comment_ranges().intersects(range) => Applicability::Unsafe,
_ => applicability,
let fix = match applicability {
Some(Applicability::Unsafe) => Fix::unsafe_edits(edit, [import_edit]),
_ => {
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
Fix::applicable_edits(edit, [import_edit], applicability)
}
};
let fix = Fix::applicable_edits(edit, [import_edit], applicability);
Ok(fix)
});
}
@@ -134,7 +138,6 @@ pub(crate) fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool
typing::is_int(binding, semantic)
}
#[expect(clippy::too_many_arguments)]
pub(crate) fn check_os_pathlib_two_arg_calls(
checker: &Checker,
call: &ExprCall,
@@ -143,7 +146,6 @@ pub(crate) fn check_os_pathlib_two_arg_calls(
second_arg: &str,
fix_enabled: bool,
violation: impl Violation,
applicability: Applicability,
) {
let range = call.range();
let mut diagnostic = checker.report_diagnostic(violation, call.func.range());
@@ -172,10 +174,10 @@ pub(crate) fn check_os_pathlib_two_arg_calls(
format!("{binding}({path_code}).{attr}({second_code})")
};
let applicability = match applicability {
Applicability::DisplayOnly => Applicability::DisplayOnly,
_ if checker.comment_ranges().intersects(range) => Applicability::Unsafe,
_ => applicability,
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
Ok(Fix::applicable_edits(
@@ -207,9 +209,3 @@ pub(crate) fn is_argument_non_default(arguments: &Arguments, name: &str, positio
.find_argument_value(name, position)
.is_some_and(|expr| !expr.is_none_literal_expr())
}
/// Returns `true` if the given call is a top-level expression in its statement.
/// This means the call's return value is not used, so return type changes don't matter.
pub(crate) fn is_top_level_expression_call(checker: &Checker) -> bool {
checker.semantic().current_expression_parent().is_none()
}

View File

@@ -1,14 +1,12 @@
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_getcwd_enabled;
use crate::{FixAvailability, Violation};
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_getcwd_enabled;
use crate::rules::flake8_use_pathlib::helpers::is_top_level_expression_call;
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.getcwd` and `os.getcwdb`.
///
@@ -39,8 +37,6 @@ use crate::{FixAvailability, Violation};
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// Additionally, the fix is marked as unsafe when the return value is used because the type changes
/// from `str` or `bytes` to a `Path` object.
///
/// ## References
/// - [Python documentation: `Path.cwd`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd)
@@ -87,10 +83,7 @@ pub(crate) fn os_getcwd(checker: &Checker, call: &ExprCall, segments: &[&str]) {
checker.semantic(),
)?;
// Unsafe when the fix would delete comments or change a used return value
let applicability = if checker.comment_ranges().intersects(range)
|| !is_top_level_expression_call(checker)
{
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe

View File

@@ -45,10 +45,6 @@ use crate::{FixAvailability, Violation};
/// behaviors is required, there's no existing `pathlib` alternative. See CPython issue
/// [#69200](https://github.com/python/cpython/issues/69200).
///
/// Additionally, the fix is marked as unsafe because `os.path.abspath()` returns `str` or `bytes` (`AnyStr`),
/// while `Path.resolve()` returns a `Path` object. This change in return type can break code that uses
/// the return value.
///
/// ## References
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
@@ -89,6 +85,6 @@ pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&s
"path",
is_fix_os_path_abspath_enabled(checker.settings()),
OsPathAbspath,
Applicability::Unsafe,
Some(Applicability::Unsafe),
);
}

View File

@@ -82,6 +82,6 @@ pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&
"p",
is_fix_os_path_basename_enabled(checker.settings()),
OsPathBasename,
Applicability::Unsafe,
Some(Applicability::Unsafe),
);
}

View File

@@ -42,10 +42,6 @@ use crate::{FixAvailability, Violation};
/// As a result, code relying on the exact string returned by `os.path.dirname`
/// may behave differently after the fix.
///
/// Additionally, the fix is marked as unsafe because `os.path.dirname()` returns `str` or `bytes` (`AnyStr`),
/// while `Path.parent` returns a `Path` object. This change in return type can break code that uses
/// the return value.
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
@@ -86,6 +82,6 @@ pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&s
"p",
is_fix_os_path_dirname_enabled(checker.settings()),
OsPathDirname,
Applicability::Unsafe,
Some(Applicability::Unsafe),
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -73,6 +72,6 @@ pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_exists_enabled(checker.settings()),
OsPathExists,
Applicability::Safe,
None,
);
}

View File

@@ -41,10 +41,6 @@ use crate::{FixAvailability, Violation};
/// directory can't be resolved: `os.path.expanduser` returns the
/// input unchanged, while `Path.expanduser` raises `RuntimeError`.
///
/// Additionally, the fix is marked as unsafe because `os.path.expanduser()` returns `str` or `bytes` (`AnyStr`),
/// while `Path.expanduser()` returns a `Path` object. This change in return type can break code that uses
/// the return value.
///
/// ## References
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
@@ -80,6 +76,6 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
"path",
is_fix_os_path_expanduser_enabled(checker.settings()),
OsPathExpanduser,
Applicability::Unsafe,
Some(Applicability::Unsafe),
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -76,6 +75,6 @@ pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getatime_enabled(checker.settings()),
OsPathGetatime,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -77,6 +76,6 @@ pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getctime_enabled(checker.settings()),
OsPathGetctime,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -77,6 +76,6 @@ pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getmtime_enabled(checker.settings()),
OsPathGetmtime,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -77,6 +76,6 @@ pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&s
"filename",
is_fix_os_path_getsize_enabled(checker.settings()),
OsPathGetsize,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -72,6 +71,6 @@ pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str
"s",
is_fix_os_path_isabs_enabled(checker.settings()),
OsPathIsabs,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -74,6 +73,6 @@ pub(crate) fn os_path_isdir(checker: &Checker, call: &ExprCall, segments: &[&str
"s",
is_fix_os_path_isdir_enabled(checker.settings()),
OsPathIsdir,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -74,6 +73,6 @@ pub(crate) fn os_path_isfile(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_isfile_enabled(checker.settings()),
OsPathIsfile,
Applicability::Safe,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -74,6 +73,6 @@ pub(crate) fn os_path_islink(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_islink_enabled(checker.settings()),
OsPathIslink,
Applicability::Safe,
None,
);
}

View File

@@ -1,13 +1,11 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_samefile_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.samefile`.
@@ -81,6 +79,5 @@ pub(crate) fn os_path_samefile(checker: &Checker, call: &ExprCall, segments: &[&
"f2",
fix_enabled,
OsPathSamefile,
Applicability::Safe,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{ExprCall, PythonVersion};
@@ -6,7 +5,6 @@ use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_readlink_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
is_top_level_expression_call,
};
use crate::{FixAvailability, Violation};
@@ -40,8 +38,6 @@ use crate::{FixAvailability, Violation};
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// Additionally, the fix is marked as unsafe when the return value is used because the type changes
/// from `str` or `bytes` (`AnyStr`) to a `Path` object.
///
/// ## References
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
@@ -86,13 +82,6 @@ pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str])
return;
}
let applicability = if !is_top_level_expression_call(checker) {
// Unsafe because the return type changes (str/bytes -> Path)
Applicability::Unsafe
} else {
Applicability::Safe
};
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -100,6 +89,6 @@ pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str])
"path",
is_fix_os_readlink_enabled(checker.settings()),
OsReadlink,
applicability,
None,
);
}

View File

@@ -1,4 +1,3 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -85,6 +84,6 @@ pub(crate) fn os_remove(checker: &Checker, call: &ExprCall, segments: &[&str]) {
"path",
is_fix_os_remove_enabled(checker.settings()),
OsRemove,
Applicability::Safe,
None,
);
}

View File

@@ -1,14 +1,12 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_rename_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
is_keyword_only_argument_non_default, is_top_level_expression_call,
is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.rename`.
@@ -40,8 +38,6 @@ use crate::{FixAvailability, Violation};
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// Additionally, the fix is marked as unsafe when the return value is used because the type changes
/// from `None` to a `Path` object.
///
/// ## References
/// - [Python documentation: `Path.rename`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename)
@@ -91,22 +87,5 @@ pub(crate) fn os_rename(checker: &Checker, call: &ExprCall, segments: &[&str]) {
&["src", "dst", "src_dir_fd", "dst_dir_fd"],
);
// Unsafe when the fix would delete comments or change a used return value
let applicability = if !is_top_level_expression_call(checker) {
// Unsafe because the return type changes (None -> Path)
Applicability::Unsafe
} else {
Applicability::Safe
};
check_os_pathlib_two_arg_calls(
checker,
call,
"rename",
"src",
"dst",
fix_enabled,
OsRename,
applicability,
);
check_os_pathlib_two_arg_calls(checker, call, "rename", "src", "dst", fix_enabled, OsRename);
}

View File

@@ -1,14 +1,12 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_replace_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_two_arg_calls, has_unknown_keywords_or_starred_expr,
is_keyword_only_argument_non_default, is_top_level_expression_call,
is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.replace`.
@@ -43,8 +41,6 @@ use crate::{FixAvailability, Violation};
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// Additionally, the fix is marked as unsafe when the return value is used because the type changes
/// from `None` to a `Path` object.
///
/// ## References
/// - [Python documentation: `Path.replace`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace)
@@ -94,14 +90,6 @@ pub(crate) fn os_replace(checker: &Checker, call: &ExprCall, segments: &[&str])
&["src", "dst", "src_dir_fd", "dst_dir_fd"],
);
// Unsafe when the fix would delete comments or change a used return value
let applicability = if !is_top_level_expression_call(checker) {
// Unsafe because the return type changes (None -> Path)
Applicability::Unsafe
} else {
Applicability::Safe
};
check_os_pathlib_two_arg_calls(
checker,
call,
@@ -110,6 +98,5 @@ pub(crate) fn os_replace(checker: &Checker, call: &ExprCall, segments: &[&str])
"dst",
fix_enabled,
OsReplace,
applicability,
);
}

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