Compare commits
1 Commits
micha/ty-p
...
gankra/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3744087c6 |
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cargo build:*)",
|
||||
"Read(//home/micha/astral/discord.py/discord/**)",
|
||||
"Bash(source:*)",
|
||||
"Bash(./target/profiling/ty check:*)",
|
||||
"Bash(tee:*)",
|
||||
"Read(//home/micha/astral/discord.py/.venv/**)",
|
||||
"Read(//home/micha/astral/discord.py/**)",
|
||||
"Bash(perf record:*)",
|
||||
"Bash(perf report:*)",
|
||||
"Bash(time:*)",
|
||||
"Bash(../ruff/target/profiling/ty check discord/audit_logs.py -vv)",
|
||||
"Bash(sed:*)",
|
||||
"Read(//home/micha/git/TypeScript/**)",
|
||||
"Bash(cargo test:*)",
|
||||
"Bash(MDTEST_TEST_FILTER='union_types.md - Union types - Unions of tuples' cargo test -p ty_python_semantic --test mdtest -- mdtest__union_types)",
|
||||
"Bash(timeout 10 cargo test --package ty_python_semantic --lib types::tests::divergent_type)",
|
||||
"Bash(timeout 30 cargo test:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(timeout 60 time:*)",
|
||||
"Bash(for i in 1 2 3 4 5)",
|
||||
"Bash(do echo \"Run $i:\")",
|
||||
"Bash(done)",
|
||||
"Bash(for i in 1 2 3)",
|
||||
"Bash(cargo fuzz run:*)",
|
||||
"Bash(timeout 60 cargo fuzz run -s none ty_check_invalid_syntax -- -timeout=1)",
|
||||
"Bash(for:*)",
|
||||
"Bash(do echo \"=== Checking $crash ===\")",
|
||||
"Bash(uvx ty@latest check \"$crash\")",
|
||||
"Bash(do echo \"=== $crash ===\")",
|
||||
"Bash(while read crash)",
|
||||
"Bash(cargo fuzz cmin:*)",
|
||||
"Bash(cargo +nightly fuzz cmin:*)",
|
||||
"Bash(timeout 120 cargo fuzz run -s none ty_check_invalid_syntax -- -timeout=1)",
|
||||
"Bash(awk:*)",
|
||||
"Bash(while read file)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(uvx ty@latest:*)",
|
||||
"Bash(do cp \"$crash\" /tmp/isolated_crash.py)",
|
||||
"Bash(echo \"=== $crash ===\")",
|
||||
"Bash(do echo \"=== test$i.py ===\")",
|
||||
"Bash(do echo \"=== Testing $crash ===\")",
|
||||
"Bash(tree:*)",
|
||||
"Bash(cut:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(xargs basename:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash({} ;)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(do)",
|
||||
"Bash(if ! grep -q \"use crate::types\" \"$f\")",
|
||||
"Bash(! grep -q \"crate::types::\" \"$f\")",
|
||||
"Bash(then)",
|
||||
"Bash(else)",
|
||||
"Bash(fi)",
|
||||
"Bash(1)",
|
||||
"Bash(__NEW_LINE__ echo \"Done\")",
|
||||
"Bash(rm:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,7 +22,6 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||
|
||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
|
||||
|
||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
|
||||
2
.github/actionlint.yaml
vendored
2
.github/actionlint.yaml
vendored
@@ -4,12 +4,10 @@
|
||||
self-hosted-runner:
|
||||
# Various runners we use that aren't recognized out-of-the-box by actionlint:
|
||||
labels:
|
||||
- depot-ubuntu-24.04-4
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-22.04-16
|
||||
- depot-ubuntu-22.04-32
|
||||
- depot-windows-2022-16
|
||||
- depot-ubuntu-22.04-arm-4
|
||||
- github-windows-2025-x86_64-8
|
||||
- github-windows-2025-x86_64-16
|
||||
- codspeed-macro
|
||||
|
||||
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -4,6 +4,5 @@
|
||||
# Enable off-by-default rules.
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
possibly-missing-import = "warn"
|
||||
unused-ignore-comment = "warn"
|
||||
division-by-zero = "warn"
|
||||
|
||||
8
.github/renovate.json5
vendored
8
.github/renovate.json5
vendored
@@ -75,6 +75,14 @@
|
||||
matchManagers: ["cargo"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
// `mkdocs-material` requires a manual update to keep the version in sync
|
||||
// with `mkdocs-material-insider`.
|
||||
// See: https://squidfunk.github.io/mkdocs-material/insiders/upgrade/
|
||||
matchManagers: ["pip_requirements"],
|
||||
matchPackageNames: ["mkdocs-material"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
groupName: "pre-commit dependencies",
|
||||
matchManagers: ["pre-commit"],
|
||||
|
||||
143
.github/workflows/ci.yaml
vendored
143
.github/workflows/ci.yaml
vendored
@@ -24,8 +24,6 @@ env:
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.14"
|
||||
NEXTEST_PROFILE: ci
|
||||
# Enable mdtests that require external dependencies
|
||||
MDTEST_EXTERNAL: "1"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -298,7 +296,7 @@ jobs:
|
||||
# sync, not just public items. Eventually we should do this for all
|
||||
# crates; for now add crates here as they are warning-clean to prevent
|
||||
# regression.
|
||||
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db -p ruff_python_formatter --document-private-items
|
||||
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
@@ -781,6 +779,8 @@ jobs:
|
||||
name: "mkdocs"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -788,6 +788,11 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
@@ -795,7 +800,11 @@ jobs:
|
||||
with:
|
||||
python-version: 3.13
|
||||
activate-environment: true
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: uv pip install -r docs/requirements.txt
|
||||
- name: "Update README File"
|
||||
run: python scripts/transform_readme.py --target mkdocs
|
||||
@@ -803,8 +812,12 @@ jobs:
|
||||
run: python scripts/generate_mkdocs.py
|
||||
- name: "Check docs formatting"
|
||||
run: python scripts/check_docs_formatted.py
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict -f mkdocs.yml
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
check-formatter-instability-and-black-similarity:
|
||||
name: "formatter instabilities and black similarity"
|
||||
@@ -952,7 +965,7 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||
@@ -960,9 +973,9 @@ jobs:
|
||||
mode: simulation
|
||||
run: cargo codspeed run
|
||||
|
||||
benchmarks-instrumented-ty-build:
|
||||
name: "benchmarks instrumented ty (build)"
|
||||
runs-on: depot-ubuntu-24.04-4
|
||||
benchmarks-instrumented-ty:
|
||||
name: "benchmarks instrumented (ty)"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
@@ -971,6 +984,9 @@ 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
|
||||
@@ -980,6 +996,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -990,64 +1007,28 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
||||
|
||||
- name: "Upload benchmark binary"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: benchmarks-instrumented-ty-binary
|
||||
path: target/codspeed/simulation/ruff_benchmark
|
||||
retention-days: 1
|
||||
|
||||
benchmarks-instrumented-ty-run:
|
||||
name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: benchmarks-instrumented-ty-build
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read # required for actions/checkout
|
||||
id-token: write # required for OIDC authentication with CodSpeed
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
benchmark:
|
||||
- "check_file|micro|anyio"
|
||||
- "attrs|hydra|datetype"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Download benchmark binary"
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v4.3.0
|
||||
with:
|
||||
name: benchmarks-instrumented-ty-binary
|
||||
path: target/codspeed/simulation/ruff_benchmark
|
||||
|
||||
- name: "Restore binary permissions"
|
||||
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
|
||||
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
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
|
||||
run: cargo codspeed run
|
||||
|
||||
benchmarks-walltime-build:
|
||||
name: "benchmarks walltime (build)"
|
||||
# We only run this job if `github.repository == 'astral-sh/ruff'`,
|
||||
# so hardcoding depot here is fine
|
||||
runs-on: depot-ubuntu-22.04-arm-4
|
||||
benchmarks-walltime:
|
||||
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
||||
runs-on: codspeed-macro
|
||||
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:
|
||||
- "medium|multithreaded"
|
||||
- "small|large"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -1068,51 +1049,7 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Upload benchmark binary"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: benchmarks-walltime-binary
|
||||
path: target/codspeed/walltime/ruff_benchmark
|
||||
retention-days: 1
|
||||
|
||||
benchmarks-walltime-run:
|
||||
name: "benchmarks walltime (${{ matrix.benchmark }})"
|
||||
runs-on: codspeed-macro
|
||||
needs: benchmarks-walltime-build
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read # required for actions/checkout
|
||||
id-token: write # required for OIDC authentication with CodSpeed
|
||||
strategy:
|
||||
matrix:
|
||||
benchmark:
|
||||
- colour_science
|
||||
- "pandas|tanjun|altair"
|
||||
- "static_frame|sympy"
|
||||
- "pydantic|multithreaded|freqtrade"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Download benchmark binary"
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: benchmarks-walltime-binary
|
||||
path: target/codspeed/walltime/ruff_benchmark
|
||||
|
||||
- name: "Restore binary permissions"
|
||||
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
|
||||
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
|
||||
@@ -1123,4 +1060,4 @@ jobs:
|
||||
CODSPEED_PERF_ENABLED: false
|
||||
with:
|
||||
mode: walltime
|
||||
run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"
|
||||
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
name: Create an issue if the daily fuzz surfaced any bugs
|
||||
runs-on: ubuntu-latest
|
||||
needs: fuzz
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
58
.github/workflows/mypy_primer.yaml
vendored
58
.github/workflows/mypy_primer.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
@@ -52,7 +47,6 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
shared-key: "mypy-primer"
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -92,7 +86,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
shared-key: "mypy-primer"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -112,54 +105,3 @@ jobs:
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
path: mypy_primer_memory.diff
|
||||
|
||||
# Runs mypy twice against the same ty version to catch any non-deterministic behavior (ideally).
|
||||
# The job is disabled for now because there are some non-deterministic diagnostics.
|
||||
mypy_primer_same_revision:
|
||||
name: Run mypy_primer on same revision
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
# TODO: Enable once we fixed the non-deterministic diagnostics
|
||||
if: false
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
shared-key: "mypy-primer"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Run determinism check
|
||||
env:
|
||||
BASE_REVISION: ${{ github.event.pull_request.head.sha }}
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
CLICOLOR_FORCE: "1"
|
||||
DIFF_FILE: mypy_primer_determinism.diff
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
|
||||
- name: Check for non-determinism
|
||||
run: |
|
||||
# Remove ANSI color codes for checking
|
||||
sed -e 's/\x1b\[[0-9;]*m//g' mypy_primer_determinism.diff > mypy_primer_determinism_clean.diff
|
||||
|
||||
# Check if there are any differences (non-determinism)
|
||||
if [ -s mypy_primer_determinism_clean.diff ]; then
|
||||
echo "ERROR: Non-deterministic output detected!"
|
||||
echo "The following differences were found when running ty twice on the same commit:"
|
||||
cat mypy_primer_determinism_clean.diff
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Output is deterministic"
|
||||
fi
|
||||
|
||||
20
.github/workflows/publish-docs.yml
vendored
20
.github/workflows/publish-docs.yml
vendored
@@ -20,6 +20,8 @@ on:
|
||||
jobs:
|
||||
mkdocs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -57,12 +59,23 @@ jobs:
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: pip install -r docs/requirements-insiders.txt
|
||||
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: "Copy README File"
|
||||
@@ -70,8 +83,13 @@ jobs:
|
||||
python scripts/transform_readme.py --target mkdocs
|
||||
python scripts/generate_mkdocs.py
|
||||
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict -f mkdocs.yml
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
- name: "Clone docs repo"
|
||||
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
41
.github/workflows/sync_typeshed.yaml
vendored
41
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,7 +16,8 @@ name: Sync typeshed
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Formats the code again
|
||||
# c. Attempts to update any snapshots that might have changed
|
||||
# (this sub-step is allowed to fail)
|
||||
# d. Commits the changes and pushes them to the same upstream branch
|
||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
@@ -197,6 +198,42 @@ jobs:
|
||||
run: |
|
||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- name: "Install Rust toolchain"
|
||||
if: ${{ success() }}
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
if: ${{ success() }}
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Update snapshots
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
cargo r \
|
||||
--profile=profiling \
|
||||
-p ty_completion_eval \
|
||||
-- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||
|
||||
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
|
||||
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
|
||||
#
|
||||
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
|
||||
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
|
||||
# We don't care about other tests failing here, we just want snapshots updated where possible,
|
||||
# so we use `|| true` here to ignore the exit code.
|
||||
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
|
||||
- name: Commit snapshot changes
|
||||
if: ${{ success() }}
|
||||
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
|
||||
- name: Push changes upstream and create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
@@ -208,7 +245,7 @@ jobs:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
24
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
24
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,6 +17,7 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
@@ -66,7 +67,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
@@ -111,13 +112,22 @@ jobs:
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload full report"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
name: full-report
|
||||
path: dist/
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
|
||||
20
.github/workflows/ty-ecosystem-report.yaml
vendored
20
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -14,6 +14,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
@@ -30,12 +31,12 @@ jobs:
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: true
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
lookup-only: false
|
||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -51,7 +52,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
@@ -69,10 +70,11 @@ jobs:
|
||||
ecosystem-diagnostics.json \
|
||||
--output dist/index.html
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload ecosystem report"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
name: full-report
|
||||
path: dist/
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}
|
||||
|
||||
5
.github/workflows/typing_conformance.yaml
vendored
5
.github/workflows/typing_conformance.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
|
||||
@@ -5,7 +5,7 @@ exclude: |
|
||||
.github/workflows/release.yml|
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty_python_types/resources/corpus/.*|
|
||||
crates/ty_python_semantic/resources/corpus/.*|
|
||||
crates/ty/docs/(configuration|rules|cli|environment).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
|
||||
91
CHANGELOG.md
91
CHANGELOG.md
@@ -1,96 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.10
|
||||
|
||||
Released on 2025-12-18.
|
||||
|
||||
### Preview features
|
||||
|
||||
- [formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
|
||||
- [formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
|
||||
- \[`flake8-implicit-str-concat`\] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
|
||||
- \[`flake8-use-pathlib`\] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
|
||||
- \[`refurb`\] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pyupgrade`\] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`eradicate`\] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
|
||||
- \[`flake8-pytest-style`\] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
|
||||
- [syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
|
||||
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
|
||||
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
|
||||
- \[`flake8-bandit`\] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@charliecloudberry](https://github.com/charliecloudberry)
|
||||
- [@charliermarsh](https://github.com/charliermarsh)
|
||||
- [@chirizxc](https://github.com/chirizxc)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@zanieb](https://github.com/zanieb)
|
||||
- [@amyreese](https://github.com/amyreese)
|
||||
- [@hauntsaninja](https://github.com/hauntsaninja)
|
||||
- [@11happy](https://github.com/11happy)
|
||||
- [@mahiro72](https://github.com/mahiro72)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@phongddo](https://github.com/phongddo)
|
||||
- [@PeterJCLaw](https://github.com/PeterJCLaw)
|
||||
|
||||
## 0.14.9
|
||||
|
||||
Released on 2025-12-11.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] New `RUF100` diagnostics for unused range suppressions ([#21783](https://github.com/astral-sh/ruff/pull/21783))
|
||||
- \[`pylint`\] Detect subclasses of builtin exceptions (`PLW0133`) ([#21382](https://github.com/astral-sh/ruff/pull/21382))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix comment placement in lambda parameters ([#21868](https://github.com/astral-sh/ruff/pull/21868))
|
||||
- Skip over trivia tokens after re-lexing ([#21895](https://github.com/astral-sh/ruff/pull/21895))
|
||||
- \[`flake8-bandit`\] Fix false positive when using non-standard `CSafeLoader` path (S506). ([#21830](https://github.com/astral-sh/ruff/pull/21830))
|
||||
- \[`flake8-bugbear`\] Accept immutable slice default arguments (`B008`) ([#21823](https://github.com/astral-sh/ruff/pull/21823))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pydocstyle`\] Suppress `D417` for parameters with `Unpack` annotations ([#21816](https://github.com/astral-sh/ruff/pull/21816))
|
||||
|
||||
### Performance
|
||||
|
||||
- Use `memchr` for computing line indexes ([#21838](https://github.com/astral-sh/ruff/pull/21838))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document `*.pyw` is included by default in preview ([#21885](https://github.com/astral-sh/ruff/pull/21885))
|
||||
- Document range suppressions, reorganize suppression docs ([#21884](https://github.com/astral-sh/ruff/pull/21884))
|
||||
- Update mkdocs-material to 9.7.0 (Insiders now free) ([#21797](https://github.com/astral-sh/ruff/pull/21797))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@Avasam](https://github.com/Avasam)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@charliermarsh](https://github.com/charliermarsh)
|
||||
- [@amyreese](https://github.com/amyreese)
|
||||
- [@phongddo](https://github.com/phongddo)
|
||||
- [@prakhar1144](https://github.com/prakhar1144)
|
||||
- [@mahiro72](https://github.com/mahiro72)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@LoicRiegel](https://github.com/LoicRiegel)
|
||||
|
||||
## 0.14.8
|
||||
|
||||
Released on 2025-12-04.
|
||||
|
||||
@@ -331,6 +331,13 @@ you addressed them.
|
||||
|
||||
## MkDocs
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
|
||||
> This means only members of the Astral organization can preview the documentation exactly as it
|
||||
> will appear in production.
|
||||
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||
@@ -344,7 +351,11 @@ To preview any changes to the documentation locally:
|
||||
1. Run the development server with:
|
||||
|
||||
```shell
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
|
||||
# For contributors.
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
|
||||
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
|
||||
221
Cargo.lock
generated
221
Cargo.lock
generated
@@ -254,21 +254,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -959,18 +944,6 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "datatest-stable"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"fancy-regex",
|
||||
"libtest-mimic 0.8.1",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.6.0"
|
||||
@@ -1004,6 +977,27 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dir-test"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62c013fe825864f3e4593f36426c1fa7a74f5603f13ca8d1af7a990c1cd94a79"
|
||||
dependencies = [
|
||||
"dir-test-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dir-test-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d42f54d7b4a6bc2400fe5b338e35d1a335787585375322f49c5d5fe7b243da7e"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@@ -1022,7 +1016,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1114,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1144,17 +1138,6 @@ dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -1255,9 +1238,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab21d7bd2c625f2064f04ce54bcb88bc57c45724cde45cba326d784e22d3f71a"
|
||||
checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1266,15 +1249,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879272b0de109e2b67b39fcfe3d25fdbba96ac07e44a254f5a0b4d7ff55340cb"
|
||||
checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"ordermap",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1642,6 +1624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"globset",
|
||||
"once_cell",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
@@ -1649,6 +1632,7 @@ dependencies = [
|
||||
"ron",
|
||||
"serde",
|
||||
"similar",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1779,7 +1763,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1934,18 +1918,6 @@ dependencies = [
|
||||
"threadpool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libtest-mimic"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap",
|
||||
"escape8259",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -2261,9 +2233,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "1.0.0"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed637741ced8fb240855d22a2b4f208dab7a06bcce73380162e5253000c16758"
|
||||
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2887,7 +2859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.10"
|
||||
version = "0.14.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3083,7 +3055,6 @@ dependencies = [
|
||||
"ty",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"url",
|
||||
]
|
||||
@@ -3130,9 +3101,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -3148,7 +3117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.10"
|
||||
version = "0.14.8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3308,7 +3277,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"countme",
|
||||
"datatest-stable",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
@@ -3378,10 +3346,8 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"datatest-stable",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_python_ast",
|
||||
@@ -3507,7 +3473,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.10"
|
||||
version = "0.14.8"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3605,7 +3571,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3622,8 +3588,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3634,7 +3600,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
"intrusive-collections",
|
||||
"inventory",
|
||||
"ordermap",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc-hash",
|
||||
@@ -3647,13 +3612,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4007,7 +3972,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4343,7 +4308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
|
||||
dependencies = [
|
||||
"ignore",
|
||||
"libtest-mimic 0.7.3",
|
||||
"libtest-mimic",
|
||||
"snapbox",
|
||||
]
|
||||
|
||||
@@ -4372,13 +4337,11 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_server",
|
||||
@@ -4393,6 +4356,7 @@ dependencies = [
|
||||
"ordermap",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4410,8 +4374,8 @@ dependencies = [
|
||||
"tempfile",
|
||||
"toml",
|
||||
"ty_ide",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -4441,33 +4405,8 @@ dependencies = [
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"ruff_db",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4505,9 +4444,7 @@ dependencies = [
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4521,12 +4458,19 @@ dependencies = [
|
||||
"bitvec",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"ruff_annotate_snippets",
|
||||
@@ -4536,7 +4480,9 @@ dependencies = [
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
@@ -4550,57 +4496,10 @@ dependencies = [
|
||||
"strsim",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_python_types"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"datatest-stable",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_test",
|
||||
@@ -4640,7 +4539,6 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_ide",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
@@ -4659,7 +4557,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"dunce",
|
||||
"insta",
|
||||
"memchr",
|
||||
"path-slash",
|
||||
@@ -4681,9 +4578,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -5130,7 +5025,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
18
Cargo.toml
18
Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# Please update rustfmt.toml when bumping the Rust edition
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
rust-version = "1.89"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -45,10 +45,8 @@ ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_module_resolver = { path = "crates/ty_module_resolver" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_python_types = { path = "crates/ty_python_types" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_static = { path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
@@ -83,14 +81,14 @@ compact_str = "0.9.0"
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
datatest-stable = { version = "0.3.3" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
etcetera = { version = "0.11.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.7.3", features = [
|
||||
get-size2 = { version = "0.7.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -131,7 +129,7 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "8.0.0" }
|
||||
ordermap = { version = "1.0.0" }
|
||||
ordermap = { version = "0.5.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -148,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 = "ce80691fa0b87dc2fd2235a26544e63e5e43d8d3", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
@@ -274,12 +272,6 @@ large_stack_arrays = "allow"
|
||||
lto = "fat"
|
||||
codegen-units = 16
|
||||
|
||||
# Profile to build a minimally sized binary for ruff/ty
|
||||
[profile.minimal-size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
# more expensive optimization passes, so we selectively
|
||||
# decrease codegen-units in some cases.
|
||||
|
||||
13
README.md
13
README.md
@@ -57,11 +57,8 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
|
||||
...and [many more](#whos-using-ruff).
|
||||
|
||||
Ruff is backed by [Astral](https://astral.sh), the creators of
|
||||
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
||||
|
||||
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
||||
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
## Testimonials
|
||||
|
||||
@@ -150,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.10/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.10/install.ps1 | iex"
|
||||
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"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -184,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.10
|
||||
rev: v0.14.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -4,7 +4,6 @@ extend-exclude = [
|
||||
"crates/ty_vendored/vendor/**/*",
|
||||
"**/resources/**/*",
|
||||
"**/snapshots/**/*",
|
||||
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
|
||||
# Completion tests tend to have a lot of incomplete
|
||||
# words naturally. It's annoying to have to make all
|
||||
# of them actually words. So just ignore typos here.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.10"
|
||||
version = "0.14.8"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -10,7 +10,7 @@ use anyhow::bail;
|
||||
use clap::builder::Styles;
|
||||
use clap::builder::styling::{AnsiColor, Effects};
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand, command};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use path_absolutize::path_dedot;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
|
||||
@@ -1440,78 +1440,6 @@ def function():
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
import os # noqa: F401
|
||||
|
||||
# ruff: disable[F401]
|
||||
import sys
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// without --ignore-noqa
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--preview"]),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// with --ignore-noqa --preview
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--ignore-noqa", "--preview"]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:2:8: F401 [*] `os` imported but unused
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
@@ -1704,100 +1632,6 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_file_level_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_range_suppression() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_multiline_comment() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
|
||||
@@ -14,6 +14,6 @@ info:
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -19,32 +19,32 @@ doctest = false
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "lexer"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "parser"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "formatter"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty"
|
||||
harness = false
|
||||
required-features = ["ty_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty_walltime"
|
||||
harness = false
|
||||
required-features = ["ty_walltime"]
|
||||
required-features = ["walltime"]
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
@@ -67,32 +67,25 @@ tracing = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
|
||||
# Enables the ruff instrumented benchmarks
|
||||
ruff_instrumented = [
|
||||
default = ["instrumented", "walltime"]
|
||||
# Enables the benchmark that should only run with codspeed's instrumented runner
|
||||
instrumented = [
|
||||
"criterion",
|
||||
"ruff_linter",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"mimalloc",
|
||||
"tikv-jemallocator",
|
||||
]
|
||||
# Enables the ty instrumented benchmarks
|
||||
ty_instrumented = [
|
||||
"criterion",
|
||||
"ty_project",
|
||||
"ruff_python_trivia",
|
||||
]
|
||||
codspeed = ["codspeed-criterion-compat"]
|
||||
# Enables the ty_walltime benchmarks
|
||||
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
# Enables benchmark that should only run with codspeed's walltime runner.
|
||||
walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true, optional = true }
|
||||
[target.'cfg(target_os = "windows")'.dev-dependencies]
|
||||
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")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true, optional = 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 }
|
||||
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13106,
|
||||
13000,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
1100,
|
||||
950,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
@@ -235,55 +235,30 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
});
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn altair(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &ALTAIR);
|
||||
#[bench(args=[&ALTAIR, &FREQTRADE, &TANJUN], sample_size=2, sample_count=3)]
|
||||
fn small(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn freqtrade(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &FREQTRADE);
|
||||
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
fn medium(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn tanjun(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &TANJUN);
|
||||
#[bench(args=[&SYMPY, &PYDANTIC], sample_size=1, sample_count=2)]
|
||||
fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn pydantic(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PYDANTIC);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 3)]
|
||||
fn static_frame(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &STATIC_FRAME);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn colour_science(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &COLOUR_SCIENCE);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn pandas(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PANDAS);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn sympy(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &SYMPY);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 3, sample_count = 8)]
|
||||
fn multithreaded(bencher: Bencher) {
|
||||
#[bench(args=[&ALTAIR], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
bencher
|
||||
.with_inputs(|| ALTAIR.setup_iteration())
|
||||
.with_inputs(|| benchmark.setup_iteration())
|
||||
.bench_local_values(|db| {
|
||||
thread_pool.install(|| {
|
||||
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
|
||||
check_project(&db, benchmark.project.name, benchmark.max_diagnostics);
|
||||
db
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
|
||||
#[cfg(feature = "instrumented")]
|
||||
pub mod criterion;
|
||||
pub mod real_world_projects;
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// Signals a [`CancellationToken`] that it should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationTokenSource {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Default for CancellationTokenSource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CancellationTokenSource {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cancelled: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_cancellation_requested(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Creates a new token that uses this source.
|
||||
pub fn token(&self) -> CancellationToken {
|
||||
CancellationToken {
|
||||
cancelled: self.cancelled.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests cancellation for operations using this token.
|
||||
pub fn cancel(&self) {
|
||||
self.cancelled
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Token signals whether an operation should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationToken {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
|
||||
use std::{fmt::Formatter, path::Path, sync::Arc};
|
||||
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
@@ -11,7 +11,6 @@ pub use self::render::{
|
||||
ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::cancellation::CancellationToken;
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
@@ -167,8 +166,28 @@ impl Diagnostic {
|
||||
/// Returns the primary message for this diagnostic.
|
||||
///
|
||||
/// A diagnostic always has a message, but it may be empty.
|
||||
///
|
||||
/// NOTE: At present, this routine will return the first primary
|
||||
/// annotation's message as the primary message when the main diagnostic
|
||||
/// message is empty. This is meant to facilitate an incremental migration
|
||||
/// in ty over to the new diagnostic data model. (The old data model
|
||||
/// didn't distinguish between messages on the entire diagnostic and
|
||||
/// messages attached to a particular span.)
|
||||
pub fn primary_message(&self) -> &str {
|
||||
self.inner.message.as_str()
|
||||
if !self.inner.message.as_str().is_empty() {
|
||||
return self.inner.message.as_str();
|
||||
}
|
||||
// FIXME: As a special case, while we're migrating ty
|
||||
// to the new diagnostic data model, we'll look for a primary
|
||||
// message from the primary annotation. This is because most
|
||||
// ty diagnostics are created with an empty diagnostic
|
||||
// message and instead attach the message to the annotation.
|
||||
// Fixing this will require touching basically every diagnostic
|
||||
// in ty, so we do it this way for now to match the old
|
||||
// semantics. ---AG
|
||||
self.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Introspects this diagnostic and returns what kind of "primary" message
|
||||
@@ -180,6 +199,18 @@ impl Diagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -193,10 +224,11 @@ impl Diagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,6 +443,11 @@ impl Diagnostic {
|
||||
self.id().is_invalid_syntax()
|
||||
}
|
||||
|
||||
/// Returns the message body to display to the user.
|
||||
pub fn body(&self) -> &str {
|
||||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the message of the first sub-diagnostic with a `Help` severity.
|
||||
///
|
||||
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
|
||||
@@ -656,6 +693,18 @@ impl SubDiagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -665,10 +714,11 @@ impl SubDiagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -838,10 +888,6 @@ impl Annotation {
|
||||
pub fn hide_snippet(&mut self, yes: bool) {
|
||||
self.hide_snippet = yes;
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
@@ -1308,8 +1354,6 @@ pub struct DisplayDiagnosticConfig {
|
||||
show_fix_diff: bool,
|
||||
/// The lowest applicability that should be shown when reporting diagnostics.
|
||||
fix_applicability: Applicability,
|
||||
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl DisplayDiagnosticConfig {
|
||||
@@ -1383,20 +1427,6 @@ impl DisplayDiagnosticConfig {
|
||||
pub fn fix_applicability(&self) -> Applicability {
|
||||
self.fix_applicability
|
||||
}
|
||||
|
||||
pub fn with_cancellation_token(
|
||||
mut self,
|
||||
token: Option<CancellationToken>,
|
||||
) -> DisplayDiagnosticConfig {
|
||||
self.cancellation_token = token;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_canceled(&self) -> bool {
|
||||
self.cancellation_token
|
||||
.as_ref()
|
||||
.is_some_and(|token| token.is_cancelled())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayDiagnosticConfig {
|
||||
@@ -1410,7 +1440,6 @@ impl Default for DisplayDiagnosticConfig {
|
||||
show_fix_status: false,
|
||||
show_fix_diff: false,
|
||||
fix_applicability: Applicability::Safe,
|
||||
cancellation_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1479,32 +1508,45 @@ pub enum DiagnosticFormat {
|
||||
pub enum ConciseMessage<'a> {
|
||||
/// A diagnostic contains a non-empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
MainDiagnostic(&'a str),
|
||||
/// A diagnostic contains an empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "old" data model.
|
||||
PrimaryAnnotation(&'a str),
|
||||
/// A diagnostic contains a non-empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
Both { main: &'a str, annotation: &'a str },
|
||||
/// A diagnostic contains an empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This indicates that the diagnostic is probably using the old
|
||||
/// model.
|
||||
Empty,
|
||||
/// A custom concise message has been provided.
|
||||
Custom(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> ConciseMessage<'a> {
|
||||
pub fn to_str(&self) -> Cow<'a, str> {
|
||||
match self {
|
||||
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
|
||||
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConciseMessage<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
ConciseMessage::MainDiagnostic(main) => {
|
||||
write!(f, "{main}")
|
||||
}
|
||||
ConciseMessage::PrimaryAnnotation(annotation) => {
|
||||
write!(f, "{annotation}")
|
||||
}
|
||||
ConciseMessage::Both { main, annotation } => {
|
||||
write!(f, "{main}: {annotation}")
|
||||
}
|
||||
ConciseMessage::Empty => Ok(()),
|
||||
ConciseMessage::Custom(message) => {
|
||||
write!(f, "{message}")
|
||||
}
|
||||
@@ -1512,16 +1554,6 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConciseMessage<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagnostic message string.
|
||||
///
|
||||
/// This is, for all intents and purposes, equivalent to a `Box<str>`.
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
|
||||
f,
|
||||
"code={code};]{body}",
|
||||
code = diag.secondary_code_or_id(),
|
||||
body = diag.concise_message(),
|
||||
body = diag.body(),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,6 @@ impl<'a> ConciseRenderer<'a> {
|
||||
|
||||
let sep = fmt_styled(":", stylesheet.separator);
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(span) = diag.primary_span() {
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -53,10 +53,6 @@ impl<'a> FullRenderer<'a> {
|
||||
.hyperlink(stylesheet.hyperlink);
|
||||
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let resolved = Resolved::new(self.resolver, diag, self.config);
|
||||
let renderable = resolved.to_renderable(self.config.context);
|
||||
for diag in renderable.diagnostics.iter() {
|
||||
|
||||
@@ -49,26 +49,14 @@ impl<'a> GithubRenderer<'a> {
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
// GitHub Actions workflow commands have constraints on error annotations:
|
||||
// - `col` and `endColumn` cannot be set if `line` and `endLine` are different
|
||||
// See: https://github.com/astral-sh/ruff/issues/22074
|
||||
if start_location.line == end_location.line {
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
",line={row},endLine={end_row}::",
|
||||
row = start_location.line,
|
||||
end_row = end_location.line,
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
@@ -87,7 +75,7 @@ impl<'a> GithubRenderer<'a> {
|
||||
write!(f, "{id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(f, " {}", diagnostic.concise_message())?;
|
||||
writeln!(f, " {}", diagnostic.body())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let description = diagnostic.concise_message();
|
||||
let description = diagnostic.body();
|
||||
let check_name = diagnostic.secondary_code_or_id();
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => "info",
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.concise_message(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: start_location.map(JsonLocation::from),
|
||||
@@ -113,7 +113,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.concise_message(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: Some(start_location.unwrap_or_default().into()),
|
||||
@@ -226,7 +226,7 @@ pub(crate) struct JsonDiagnostic<'a> {
|
||||
filename: Option<&'a str>,
|
||||
fix: Option<JsonFix<'a>>,
|
||||
location: Option<JsonLocation>,
|
||||
message: ConciseMessage<'a>,
|
||||
message: &'a str,
|
||||
noqa_row: Option<OneIndexed>,
|
||||
url: Option<&'a str>,
|
||||
}
|
||||
|
||||
@@ -56,17 +56,17 @@ impl<'a> JunitRenderer<'a> {
|
||||
start_location: location,
|
||||
} = diagnostic;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(diagnostic.concise_message().to_str());
|
||||
status.set_message(diagnostic.body());
|
||||
|
||||
if let Some(location) = location {
|
||||
status.set_description(format!(
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.line,
|
||||
col = location.column,
|
||||
body = diagnostic.concise_message()
|
||||
body = diagnostic.body()
|
||||
));
|
||||
} else {
|
||||
status.set_description(diagnostic.concise_message().to_str());
|
||||
status.set_description(diagnostic.body());
|
||||
}
|
||||
|
||||
let code = diagnostic
|
||||
|
||||
@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
|
||||
f,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
path = filename,
|
||||
body = diagnostic.concise_message()
|
||||
body = diagnostic.body()
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic};
|
||||
use crate::diagnostic::Diagnostic;
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -76,7 +76,7 @@ fn diagnostic_to_rdjson<'a>(
|
||||
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
|
||||
|
||||
RdjsonDiagnostic {
|
||||
message: diagnostic.concise_message(),
|
||||
message: diagnostic.body(),
|
||||
location,
|
||||
code: RdjsonCode {
|
||||
value: diagnostic
|
||||
@@ -155,7 +155,7 @@ struct RdjsonDiagnostic<'a> {
|
||||
code: RdjsonCode<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
location: Option<RdjsonLocation<'a>>,
|
||||
message: ConciseMessage<'a>,
|
||||
message: &'a str,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
suggestions: Vec<RdjsonSuggestion<'a>>,
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,endLine=2::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,endLine=4::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -12,7 +12,6 @@ use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub mod cancellation;
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
pub mod file_revision;
|
||||
|
||||
@@ -275,16 +275,16 @@ impl OsSystem {
|
||||
/// instead of at least one system call for each component between `path` and `prefix`.
|
||||
///
|
||||
/// However, using `canonicalize` to resolve the path's casing doesn't work in two cases:
|
||||
/// * if `path` is a symlink, `canonicalize` returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive, `canonicalize` returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` returns `\\?\UNC\server\share`).
|
||||
/// * if `path` is a symlink because `canonicalize` then returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive because `canonicalize` then returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` then returns `\\?\UNC\server\share`).
|
||||
///
|
||||
/// Symlinks and mapped network drives should be rare enough that this fast path is worth trying first,
|
||||
/// even if it comes at a cost for those rare use cases.
|
||||
fn path_exists_case_sensitive_fast(&self, path: &SystemPath) -> Option<bool> {
|
||||
// This is a more forgiving version of `dunce::simplified` that removes all `\\?\` prefixes on Windows.
|
||||
// We use this more forgiving version because we don't intend on using either path for anything other than comparison
|
||||
// and the prefix is only relevant when passing the path to other programs and it's longer than 200 something
|
||||
// and the prefix is only relevant when passing the path to other programs and its longer than 200 something
|
||||
// characters.
|
||||
fn simplify_ignore_verbatim(path: &SystemPath) -> &SystemPath {
|
||||
if cfg!(windows) {
|
||||
@@ -298,7 +298,9 @@ impl OsSystem {
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(canonicalized) = path.as_std_path().canonicalize() else {
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
let Ok(canonicalized) = simplified.as_std_path().canonicalize() else {
|
||||
// The path doesn't exist or can't be accessed. The path doesn't exist.
|
||||
return Some(false);
|
||||
};
|
||||
@@ -307,13 +309,12 @@ impl OsSystem {
|
||||
// The original path is valid UTF8 but the canonicalized path isn't. This definitely suggests
|
||||
// that a symlink is involved. Fall back to the slow path.
|
||||
tracing::debug!(
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{path}` is not valid UTF-8"
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{simplified}` is not valid UTF-8"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let simplified_canonicalized = simplify_ignore_verbatim(&canonicalized);
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
// Test if the paths differ by anything other than casing. If so, that suggests that
|
||||
// `path` pointed to a symlink (or some other none reversible path normalization happened).
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use glob::PatternError;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -21,44 +20,18 @@ use super::walk_directory::WalkDirectoryBuilder;
|
||||
///
|
||||
/// ## Warning
|
||||
/// Don't use this system for production code. It's intended for testing only.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestSystem {
|
||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||
/// Environment variable overrides. If a key is present here, it takes precedence
|
||||
/// over the inner system's environment variables.
|
||||
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
||||
}
|
||||
|
||||
impl Clone for TestSystem {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
env_overrides: self.env_overrides.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSystem {
|
||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(inner),
|
||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an environment variable override. This takes precedence over the inner system.
|
||||
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.env_overrides
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(name.into(), Some(value.into()));
|
||||
}
|
||||
|
||||
/// Removes an environment variable override, making it appear as not set.
|
||||
pub fn remove_env_var(&self, name: impl Into<String>) {
|
||||
self.env_overrides.lock().unwrap().insert(name.into(), None);
|
||||
}
|
||||
|
||||
/// Returns the [`InMemorySystem`].
|
||||
///
|
||||
/// ## Panics
|
||||
@@ -174,18 +147,6 @@ impl System for TestSystem {
|
||||
self.system().case_sensitivity()
|
||||
}
|
||||
|
||||
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||
// Check overrides first
|
||||
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
||||
return match override_value {
|
||||
Some(value) => Ok(value.clone()),
|
||||
None => Err(std::env::VarError::NotPresent),
|
||||
};
|
||||
}
|
||||
// Fall back to inner system
|
||||
self.system().env_var(name)
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
@@ -195,7 +156,6 @@ impl Default for TestSystem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InMemorySystem::default()),
|
||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ license = { workspace = true }
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_python_types = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use anyhow::bail;
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_python_trivia::textwrap;
|
||||
use ty_project::metadata::Options;
|
||||
|
||||
use crate::{
|
||||
@@ -146,8 +144,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
output.push_str("!!! warning \"Deprecated\"\n");
|
||||
output.push_str(" This option has been deprecated");
|
||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
||||
output.push_str("> This option has been deprecated");
|
||||
|
||||
if let Some(since) = deprecated.since {
|
||||
write!(output, " in {since}").unwrap();
|
||||
@@ -167,69 +165,61 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
let _ = writeln!(output, "**Default value**: `{}`", field.default);
|
||||
output.push('\n');
|
||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||
|
||||
output.push('\n');
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
||||
output.push_str(&format_example(
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
for configuration_file in [ConfigurationFile::PyprojectToml, ConfigurationFile::TyToml] {
|
||||
let (header, example) =
|
||||
format_snippet(field.scope, field.example, parents, configuration_file);
|
||||
output.push_str(&format_tab(configuration_file.name(), &header, &example));
|
||||
|
||||
output.push('\n');
|
||||
fn format_example(header: &str, content: &str) -> String {
|
||||
if header.is_empty() {
|
||||
format!("```toml\n{content}\n```\n",)
|
||||
} else {
|
||||
format!("```toml\n{header}\n{content}\n```\n",)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
let header = if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\n {header}")
|
||||
};
|
||||
format!(
|
||||
"=== \"{}\"\n\n ```toml{}\n{}\n ```\n",
|
||||
tab_name,
|
||||
header,
|
||||
textwrap::indent(content, " ")
|
||||
)
|
||||
}
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ty.rules]`.
|
||||
fn format_snippet<'a>(
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
scope: Option<&str>,
|
||||
example: &'a str,
|
||||
example: &str,
|
||||
parents: &[Set],
|
||||
configuration: ConfigurationFile,
|
||||
) -> (String, Cow<'a, str>) {
|
||||
let mut example = Cow::Borrowed(example);
|
||||
) -> String {
|
||||
let tool_parent = match configuration {
|
||||
ConfigurationFile::PyprojectToml => Some("tool.ty"),
|
||||
ConfigurationFile::TyToml => None,
|
||||
};
|
||||
|
||||
let header = configuration
|
||||
.parent_table()
|
||||
let header = tool_parent
|
||||
.into_iter()
|
||||
.chain(parents.iter().filter_map(|parent| parent.name()))
|
||||
.chain(scope)
|
||||
.join(".");
|
||||
|
||||
// Rewrite examples starting with `[tool.ty]` or `[[tool.ty]]` to their `ty.toml` equivalent.
|
||||
if matches!(configuration, ConfigurationFile::TyToml) {
|
||||
example = example.replace("[tool.ty.", "[").into();
|
||||
}
|
||||
|
||||
// Ex) `[[tool.ty.xx]]`
|
||||
if example.starts_with(&format!("[[{header}")) {
|
||||
return (String::new(), example);
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Ex) `[tool.ty.rules]`
|
||||
if example.starts_with(&format!("[{header}")) {
|
||||
return (String::new(), example);
|
||||
return String::new();
|
||||
}
|
||||
|
||||
if header.is_empty() {
|
||||
(String::new(), example)
|
||||
String::new()
|
||||
} else {
|
||||
(format!("[{header}]"), example)
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,25 +242,10 @@ impl Visit for CollectOptionsVisitor {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ConfigurationFile {
|
||||
PyprojectToml,
|
||||
#[expect(dead_code)]
|
||||
TyToml,
|
||||
}
|
||||
|
||||
impl ConfigurationFile {
|
||||
const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::PyprojectToml => "pyproject.toml",
|
||||
Self::TyToml => "ty.toml",
|
||||
}
|
||||
}
|
||||
|
||||
const fn parent_table(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::PyprojectToml => Some("tool.ty"),
|
||||
Self::TyToml => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
}
|
||||
|
||||
fn generate_markdown() -> String {
|
||||
let registry = ty_python_types::default_lint_registry();
|
||||
let registry = ty_python_semantic::default_lint_registry();
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -63,7 +63,12 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
lints.sort_by_key(|a| a.name());
|
||||
lints.sort_by(|a, b| {
|
||||
a.default_level()
|
||||
.cmp(&b.default_level())
|
||||
.reverse()
|
||||
.then_with(|| a.name().cmp(&b.name()))
|
||||
});
|
||||
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
@@ -114,7 +119,7 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Edit {
|
||||
|
||||
/// Creates an edit that replaces the content in `range` with `content`.
|
||||
pub fn range_replacement(content: String, range: TextRange) -> Self {
|
||||
debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
|
||||
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
|
||||
|
||||
Self {
|
||||
content: Some(Box::from(content)),
|
||||
|
||||
@@ -337,7 +337,7 @@ macro_rules! best_fitting {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer};
|
||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer, write};
|
||||
|
||||
struct TestFormat;
|
||||
|
||||
@@ -385,8 +385,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn best_fitting_variants_print_as_lists() {
|
||||
use crate::Formatted;
|
||||
use crate::prelude::*;
|
||||
use crate::{Formatted, format, format_args};
|
||||
|
||||
// The second variant below should be selected when printing at a width of 30
|
||||
let formatted_best_fitting = format!(
|
||||
|
||||
@@ -16,9 +16,7 @@ ruff_linter = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_python_types = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::visitor::source_order::{
|
||||
SourceOrderVisitor, walk_expr, walk_module, walk_stmt,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Expr, Mod, Stmt};
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_python_semantic::ModuleName;
|
||||
|
||||
/// Collect all imports for a given Python file.
|
||||
#[derive(Default, Debug)]
|
||||
|
||||
@@ -7,13 +7,11 @@ use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{SearchPathSettings, SearchPaths};
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
AnalysisSettings, Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform,
|
||||
PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin,
|
||||
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
|
||||
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
|
||||
};
|
||||
use ty_python_types::default_lint_registry;
|
||||
|
||||
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
|
||||
let mut builder = VendoredFileSystemBuilder::new(CompressionMethod::Stored);
|
||||
@@ -28,7 +26,6 @@ pub struct ModuleDb {
|
||||
files: Files,
|
||||
system: OsSystem,
|
||||
rule_selection: Arc<RuleSelection>,
|
||||
analysis_settings: Arc<AnalysisSettings>,
|
||||
}
|
||||
|
||||
impl ModuleDb {
|
||||
@@ -88,13 +85,6 @@ impl SourceDb for ModuleDb {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for ModuleDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for ModuleDb {
|
||||
fn should_check_file(&self, file: File) -> bool {
|
||||
@@ -112,10 +102,6 @@ impl Db for ModuleDb {
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings {
|
||||
&self.analysis_settings
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_db::files::{File, FilePath, system_path_to_file};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ty_module_resolver::{
|
||||
use ty_python_semantic::{
|
||||
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.10"
|
||||
version = "0.14.8"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -28,11 +28,9 @@ yaml.load("{}", SafeLoader)
|
||||
yaml.load("{}", yaml.SafeLoader)
|
||||
yaml.load("{}", CSafeLoader)
|
||||
yaml.load("{}", yaml.CSafeLoader)
|
||||
yaml.load("{}", yaml.cyaml.CSafeLoader)
|
||||
yaml.load("{}", NewSafeLoader)
|
||||
yaml.load("{}", Loader=SafeLoader)
|
||||
yaml.load("{}", Loader=yaml.SafeLoader)
|
||||
yaml.load("{}", Loader=CSafeLoader)
|
||||
yaml.load("{}", Loader=yaml.CSafeLoader)
|
||||
yaml.load("{}", Loader=yaml.cyaml.CSafeLoader)
|
||||
yaml.load("{}", Loader=NewSafeLoader)
|
||||
|
||||
@@ -199,9 +199,6 @@ def bytes_okay(value=bytes(1)):
|
||||
def int_okay(value=int("12")):
|
||||
pass
|
||||
|
||||
# Allow immutable slice()
|
||||
def slice_okay(value=slice(1,2)):
|
||||
pass
|
||||
|
||||
# Allow immutable complex() value
|
||||
def complex_okay(value=complex(1,2)):
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
facts = (
|
||||
"Lobsters have blue blood.",
|
||||
"The liver is the only human organ that can fully regenerate itself.",
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
)
|
||||
|
||||
facts = [
|
||||
"Lobsters have blue blood.",
|
||||
"The liver is the only human organ that can fully regenerate itself.",
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
]
|
||||
|
||||
facts = {
|
||||
"Lobsters have blue blood.",
|
||||
"The liver is the only human organ that can fully regenerate itself.",
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
}
|
||||
|
||||
facts = {
|
||||
(
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||
),
|
||||
}
|
||||
|
||||
facts = (
|
||||
"Octopuses have three hearts."
|
||||
# Missing comma here.
|
||||
"Honey never spoils.",
|
||||
)
|
||||
|
||||
facts = [
|
||||
"Octopuses have three hearts."
|
||||
# Missing comma here.
|
||||
"Honey never spoils.",
|
||||
]
|
||||
|
||||
facts = {
|
||||
"Octopuses have three hearts."
|
||||
# Missing comma here.
|
||||
"Honey never spoils.",
|
||||
}
|
||||
|
||||
facts = (
|
||||
(
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||
),
|
||||
)
|
||||
|
||||
facts = [
|
||||
(
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon."
|
||||
),
|
||||
]
|
||||
|
||||
facts = (
|
||||
"Lobsters have blue blood.\n"
|
||||
"The liver is the only human organ that can fully regenerate itself.\n"
|
||||
"Clarinets are made almost entirely out of wood from the mpingo tree.\n"
|
||||
"In 1971, astronaut Alan Shepard played golf on the moon.\n"
|
||||
)
|
||||
@@ -9,15 +9,3 @@ def test_ok():
|
||||
def test_error():
|
||||
with pytest.raises(UnicodeError):
|
||||
pass
|
||||
|
||||
def test_match_only():
|
||||
with pytest.raises(match="some error message"):
|
||||
pass
|
||||
|
||||
def test_check_only():
|
||||
with pytest.raises(check=lambda e: True):
|
||||
pass
|
||||
|
||||
def test_match_and_check():
|
||||
with pytest.raises(match="some error message", check=lambda e: True):
|
||||
pass
|
||||
|
||||
@@ -136,38 +136,4 @@ os.chmod("pth1_file", 0o700, None, True, 1, *[1], **{"x": 1}, foo=1)
|
||||
os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||
os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1)
|
||||
|
||||
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/21794
|
||||
import sys
|
||||
|
||||
if os.rename("pth1.py", "pth1.py.bak"):
|
||||
print("rename: truthy")
|
||||
else:
|
||||
print("rename: falsey")
|
||||
|
||||
if os.replace("pth1.py.bak", "pth1.py"):
|
||||
print("replace: truthy")
|
||||
else:
|
||||
print("replace: falsey")
|
||||
|
||||
try:
|
||||
for _ in os.getcwd():
|
||||
print("getcwd: iterable")
|
||||
break
|
||||
except TypeError as e:
|
||||
print("getcwd: not iterable")
|
||||
|
||||
try:
|
||||
for _ in os.getcwdb():
|
||||
print("getcwdb: iterable")
|
||||
break
|
||||
except TypeError as e:
|
||||
print("getcwdb: not iterable")
|
||||
|
||||
try:
|
||||
for _ in os.readlink(sys.executable):
|
||||
print("readlink: iterable")
|
||||
break
|
||||
except TypeError as e:
|
||||
print("readlink: not iterable")
|
||||
os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1)
|
||||
@@ -218,26 +218,3 @@ def should_not_fail(payload, Args):
|
||||
Args:
|
||||
The other arguments.
|
||||
"""
|
||||
|
||||
|
||||
# Test cases for Unpack[TypedDict] kwargs
|
||||
from typing import TypedDict
|
||||
from typing_extensions import Unpack
|
||||
|
||||
class User(TypedDict):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
def function_with_unpack_args_should_not_fail(query: str, **kwargs: Unpack[User]):
|
||||
"""Function with Unpack kwargs.
|
||||
|
||||
Args:
|
||||
query: some arg
|
||||
"""
|
||||
|
||||
def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
|
||||
"""Function with Unpack kwargs but missing query arg documentation.
|
||||
|
||||
Args:
|
||||
**kwargs: keyword arguments
|
||||
"""
|
||||
|
||||
@@ -2,40 +2,15 @@ from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
|
||||
|
||||
class MyError(Exception):
|
||||
...
|
||||
|
||||
|
||||
class MySubError(MyError):
|
||||
...
|
||||
|
||||
|
||||
class MyValueError(ValueError):
|
||||
...
|
||||
|
||||
|
||||
class MyUserWarning(UserWarning):
|
||||
...
|
||||
|
||||
|
||||
# Violation test cases with builtin errors: PLW0133
|
||||
|
||||
|
||||
# Test case 1: Useless exception statement
|
||||
def func():
|
||||
AssertionError("This is an assertion error") # PLW0133
|
||||
MyError("This is a custom error") # PLW0133
|
||||
MySubError("This is a custom error") # PLW0133
|
||||
MyValueError("This is a custom value error") # PLW0133
|
||||
|
||||
|
||||
# Test case 2: Useless exception statement in try-except block
|
||||
def func():
|
||||
try:
|
||||
Exception("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
except Exception as err:
|
||||
pass
|
||||
|
||||
@@ -44,9 +19,6 @@ def func():
|
||||
def func():
|
||||
if True:
|
||||
RuntimeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 4: Useless exception statement in class
|
||||
@@ -54,18 +26,12 @@ def func():
|
||||
class Class:
|
||||
def __init__(self):
|
||||
TypeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 5: Useless exception statement in function
|
||||
def func():
|
||||
def inner():
|
||||
IndexError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
inner()
|
||||
|
||||
@@ -74,9 +40,6 @@ def func():
|
||||
def func():
|
||||
while True:
|
||||
KeyError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 7: Useless exception statement in abstract class
|
||||
@@ -85,58 +48,27 @@ def func():
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
NotImplementedError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 8: Useless exception statement inside context manager
|
||||
def func():
|
||||
with suppress(Exception):
|
||||
with suppress(AttributeError):
|
||||
AttributeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 9: Useless exception statement in parentheses
|
||||
def func():
|
||||
(RuntimeError("This is an exception")) # PLW0133
|
||||
(MyError("This is an exception")) # PLW0133
|
||||
(MySubError("This is an exception")) # PLW0133
|
||||
(MyValueError("This is an exception")) # PLW0133
|
||||
|
||||
|
||||
# Test case 10: Useless exception statement in continuation
|
||||
def func():
|
||||
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
|
||||
|
||||
# Test case 11: Useless warning statement
|
||||
def func():
|
||||
UserWarning("This is a user warning") # PLW0133
|
||||
MyUserWarning("This is a custom user warning") # PLW0133
|
||||
|
||||
|
||||
# Test case 12: Useless exception statement at module level
|
||||
import builtins
|
||||
|
||||
builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
|
||||
PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
|
||||
MyError("This is an exception") # PLW0133
|
||||
|
||||
MySubError("This is an exception") # PLW0133
|
||||
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
UserWarning("This is a user warning") # PLW0133
|
||||
|
||||
MyUserWarning("This is a custom user warning") # PLW0133
|
||||
UserWarning("This is an assertion error") # PLW0133
|
||||
|
||||
|
||||
# Non-violation test cases: PLW0133
|
||||
@@ -187,3 +119,10 @@ def func():
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
raise AttributeError("This is an exception") # OK
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
builtins.TypeError("still an exception even though it's an Attribute")
|
||||
|
||||
PythonFinalizationError("Added in Python 3.13")
|
||||
|
||||
@@ -132,6 +132,7 @@ async def c():
|
||||
# Non-errors
|
||||
###
|
||||
|
||||
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
||||
"\N{snowman} {}".format(a)
|
||||
|
||||
"{".format(a)
|
||||
@@ -275,6 +276,3 @@ if __name__ == "__main__":
|
||||
number = 0
|
||||
string = "{}".format(number := number + 1)
|
||||
print(string)
|
||||
|
||||
# Unicode escape
|
||||
"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
|
||||
@@ -138,6 +138,5 @@ with open("file.txt", encoding="utf-8") as f:
|
||||
with open("file.txt", encoding="utf-8") as f:
|
||||
contents = process_contents(f.read())
|
||||
|
||||
with open("file1.txt", encoding="utf-8") as f:
|
||||
with open("file.txt", encoding="utf-8") as f:
|
||||
contents: str = process_contents(f.read())
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
with Path("file.txt").open() as f:
|
||||
contents = f.read()
|
||||
|
||||
with Path("file.txt").open("r") as f:
|
||||
contents = f.read()
|
||||
@@ -1,26 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
with Path("file.txt").open("w") as f:
|
||||
f.write("test")
|
||||
|
||||
with Path("file.txt").open("wb") as f:
|
||||
f.write(b"test")
|
||||
|
||||
with Path("file.txt").open(mode="w") as f:
|
||||
f.write("test")
|
||||
|
||||
with Path("file.txt").open("w", encoding="utf8") as f:
|
||||
f.write("test")
|
||||
|
||||
with Path("file.txt").open("w", errors="ignore") as f:
|
||||
f.write("test")
|
||||
|
||||
with Path(foo()).open("w") as f:
|
||||
f.write("test")
|
||||
|
||||
p = Path("file.txt")
|
||||
with p.open("w") as f:
|
||||
f.write("test")
|
||||
|
||||
with Path("foo", "bar", "baz").open("w") as f:
|
||||
f.write("test")
|
||||
@@ -1,111 +0,0 @@
|
||||
def f():
|
||||
# These should both be ignored by the range suppression.
|
||||
# ruff: disable[E741, F841]
|
||||
I = 1
|
||||
# ruff: enable[E741, F841]
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the implicit range suppression.
|
||||
# Should also generate an "unmatched suppression" warning.
|
||||
# ruff:disable[E741,F841]
|
||||
I = 1
|
||||
|
||||
|
||||
def f():
|
||||
# Neither warning is ignored, and an "unmatched suppression"
|
||||
# should be generated.
|
||||
I = 1
|
||||
# ruff: enable[E741, F841]
|
||||
|
||||
|
||||
def f():
|
||||
# One should be ignored by the range suppression, and
|
||||
# the other logged to the user.
|
||||
# ruff: disable[E741]
|
||||
I = 1
|
||||
# ruff: enable[E741]
|
||||
|
||||
|
||||
def f():
|
||||
# Test interleaved range suppressions. The first and last
|
||||
# lines should each log a different warning, while the
|
||||
# middle line should be completely silenced.
|
||||
# ruff: disable[E741]
|
||||
l = 0
|
||||
# ruff: disable[F841]
|
||||
O = 1
|
||||
# ruff: enable[E741]
|
||||
I = 2
|
||||
# ruff: enable[F841]
|
||||
|
||||
|
||||
def f():
|
||||
# Neither of these are ignored and warnings are
|
||||
# logged to user
|
||||
# ruff: disable[E501]
|
||||
I = 1
|
||||
# ruff: enable[E501]
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the range suppression,
|
||||
# and an unusued noqa diagnostic should be logged.
|
||||
# ruff:disable[E741,F841]
|
||||
I = 1 # noqa: E741,F841
|
||||
# ruff:enable[E741,F841]
|
||||
|
||||
|
||||
def f():
|
||||
# TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
# ruff: disable[F841, F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Overlapping range suppressions, one should be marked as used,
|
||||
# and the other should trigger an unused suppression diagnostic
|
||||
# ruff: disable[F841]
|
||||
# ruff: disable[F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but only one is used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but only two are used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
I = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but none are used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
print("hello")
|
||||
|
||||
|
||||
def f():
|
||||
# Unknown rule codes
|
||||
# ruff: disable[YF829]
|
||||
# ruff: disable[F841, RQW320]
|
||||
value = 0
|
||||
# ruff: enable[F841, RQW320]
|
||||
# ruff: enable[YF829]
|
||||
|
||||
|
||||
def f():
|
||||
# External rule codes should be ignored
|
||||
# ruff: disable[TK421]
|
||||
print("hello")
|
||||
# ruff: enable[TK421]
|
||||
|
||||
|
||||
def f():
|
||||
# Empty or missing rule codes
|
||||
# ruff: disable
|
||||
# ruff: disable[]
|
||||
print("hello")
|
||||
@@ -1,38 +0,0 @@
|
||||
a: int = 1
|
||||
def f1():
|
||||
global a
|
||||
a: str = "foo" # error
|
||||
|
||||
b: int = 1
|
||||
def outer():
|
||||
def inner():
|
||||
global b
|
||||
b: str = "nested" # error
|
||||
|
||||
c: int = 1
|
||||
def f2():
|
||||
global c
|
||||
c: list[str] = [] # error
|
||||
|
||||
d: int = 1
|
||||
def f3():
|
||||
global d
|
||||
d: str # error
|
||||
|
||||
e: int = 1
|
||||
def f4():
|
||||
e: str = "happy" # okay
|
||||
|
||||
global f
|
||||
f: int = 1 # okay
|
||||
|
||||
g: int = 1
|
||||
global g # error
|
||||
|
||||
class C:
|
||||
x: str
|
||||
global x # error
|
||||
|
||||
class D:
|
||||
global x # error
|
||||
x: str
|
||||
@@ -214,13 +214,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
||||
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
||||
checker,
|
||||
expr,
|
||||
elts,
|
||||
);
|
||||
}
|
||||
if ctx.is_store() {
|
||||
let check_too_many_expressions =
|
||||
checker.is_rule_enabled(Rule::ExpressionsInStarAssignment);
|
||||
@@ -1336,13 +1329,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
}
|
||||
}
|
||||
Expr::Set(set) => {
|
||||
if checker.is_rule_enabled(Rule::ImplicitStringConcatenationInCollectionLiteral) {
|
||||
flake8_implicit_str_concat::rules::implicit_string_concatenation_in_collection_literal(
|
||||
checker,
|
||||
expr,
|
||||
&set.elts,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::DuplicateValue) {
|
||||
flake8_bugbear::rules::duplicate_value(checker, set);
|
||||
}
|
||||
|
||||
@@ -437,15 +437,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Tokens`] for the parsed source file.
|
||||
///
|
||||
///
|
||||
/// Unlike [`Self::tokens`], this method always returns
|
||||
/// the tokens for the current file, even when within a parsed type annotation.
|
||||
pub(crate) fn source_tokens(&self) -> &'a Tokens {
|
||||
self.parsed.tokens()
|
||||
}
|
||||
|
||||
/// The [`Locator`] for the current file, which enables extraction of source code from byte
|
||||
/// offsets.
|
||||
pub(crate) const fn locator(&self) -> &'a Locator<'a> {
|
||||
|
||||
@@ -12,20 +12,17 @@ use crate::fix::edits::delete_comment;
|
||||
use crate::noqa::{
|
||||
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
||||
};
|
||||
use crate::preview::is_range_suppressions_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::pygrep_hooks;
|
||||
use crate::rules::ruff;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Edit, Fix, Locator};
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
/// RUF100
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_noqa(
|
||||
context: &mut LintContext,
|
||||
path: &Path,
|
||||
@@ -34,7 +31,6 @@ pub(crate) fn check_noqa(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
analyze_directives: bool,
|
||||
settings: &LinterSettings,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<usize> {
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let file_noqa_directives =
|
||||
@@ -44,7 +40,7 @@ pub(crate) fn check_noqa(
|
||||
let mut noqa_directives =
|
||||
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
||||
|
||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
|
||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -64,19 +60,11 @@ pub(crate) fn check_noqa(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply file-level suppressions first
|
||||
if exemption.contains_secondary_code(code) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply ranged suppressions next
|
||||
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply end-of-line noqa suppressions last
|
||||
let noqa_offsets = diagnostic
|
||||
.parent()
|
||||
.into_iter()
|
||||
@@ -119,9 +107,6 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics for unused/invalid range suppressions
|
||||
suppressions.check_suppressions(context, locator);
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||
// suppressed.
|
||||
if context.is_rule_enabled(Rule::UnusedNOQA)
|
||||
@@ -143,13 +128,8 @@ pub(crate) fn check_noqa(
|
||||
Directive::All(directive) => {
|
||||
if matches.is_empty() {
|
||||
let edit = delete_comment(directive.range(), locator);
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: None,
|
||||
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
let mut diagnostic = context
|
||||
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
@@ -244,7 +224,6 @@ pub(crate) fn check_noqa(
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
}),
|
||||
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
|
||||
@@ -454,7 +454,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8ImplicitStrConcat, "001") => rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation,
|
||||
(Flake8ImplicitStrConcat, "002") => rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
|
||||
(Flake8ImplicitStrConcat, "003") => rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
|
||||
(Flake8ImplicitStrConcat, "004") => rules::flake8_implicit_str_concat::rules::ImplicitStringConcatenationInCollectionLiteral,
|
||||
|
||||
// flake8-print
|
||||
(Flake8Print, "1") => rules::flake8_print::rules::Print,
|
||||
@@ -1064,8 +1063,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
||||
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
||||
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
||||
(Ruff, "103") => rules::ruff::rules::InvalidSuppressionComment,
|
||||
(Ruff, "104") => rules::ruff::rules::UnmatchedSuppressionComment,
|
||||
|
||||
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::token::{self, Tokens, parenthesized_range};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::textwrap::dedent_to;
|
||||
use ruff_python_trivia::{
|
||||
PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content, is_python_whitespace,
|
||||
CommentRanges, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content,
|
||||
is_python_whitespace,
|
||||
};
|
||||
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
@@ -208,7 +209,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
source: &str,
|
||||
tokens: &Tokens,
|
||||
comment_ranges: &CommentRanges,
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
@@ -223,7 +224,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
.context("Unable to find argument")?;
|
||||
|
||||
let parenthesized_range =
|
||||
token::parenthesized_range(arg.value().into(), arguments.into(), tokens)
|
||||
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
|
||||
.unwrap_or(arg.range());
|
||||
|
||||
if !after.is_empty() {
|
||||
@@ -269,14 +270,25 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
///
|
||||
/// The new argument will be inserted before the first existing keyword argument in `arguments`, if
|
||||
/// there are any present. Otherwise, the new argument is added to the end of the argument list.
|
||||
pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Tokens) -> Edit {
|
||||
pub(crate) fn add_argument(
|
||||
argument: &str,
|
||||
arguments: &Arguments,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Edit {
|
||||
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
|
||||
let keyword = parenthesized_range(value.into(), arguments.into(), tokens).unwrap_or(*range);
|
||||
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
|
||||
.unwrap_or(*range);
|
||||
Edit::insertion(format!("{argument}, "), keyword.start())
|
||||
} else if let Some(last) = arguments.arguments_source_order().last() {
|
||||
// Case 1: existing arguments, so append after the last argument.
|
||||
let last = parenthesized_range(last.value().into(), arguments.into(), tokens)
|
||||
.unwrap_or(last.range());
|
||||
let last = parenthesized_range(
|
||||
last.value().into(),
|
||||
arguments.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(last.range());
|
||||
Edit::insertion(format!(", {argument}"), last.end())
|
||||
} else {
|
||||
// Case 2: no arguments. Add argument, without any trailing comma.
|
||||
@@ -286,7 +298,12 @@ pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Token
|
||||
|
||||
/// Generic function to add a (regular) parameter to a function definition.
|
||||
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
||||
if let Some(last) = parameters.args.iter().rfind(|arg| arg.default.is_none()) {
|
||||
if let Some(last) = parameters
|
||||
.args
|
||||
.iter()
|
||||
.filter(|arg| arg.default.is_none())
|
||||
.next_back()
|
||||
{
|
||||
// Case 1: at least one regular parameter, so append after the last one.
|
||||
Edit::insertion(format!(", {parameter}"), last.end())
|
||||
} else if !parameters.args.is_empty() {
|
||||
|
||||
@@ -32,7 +32,6 @@ use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::settings::{LinterSettings, TargetVersion, flags};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Locator, directives, fs};
|
||||
|
||||
pub(crate) mod float;
|
||||
@@ -129,7 +128,6 @@ pub fn check_path(
|
||||
source_type: PySourceType,
|
||||
parsed: &Parsed<ModModule>,
|
||||
target_version: TargetVersion,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<Diagnostic> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut context = LintContext::new(path, locator.contents(), settings);
|
||||
@@ -341,7 +339,6 @@ pub fn check_path(
|
||||
&directives.noqa_line_for,
|
||||
parsed.has_valid_syntax(),
|
||||
settings,
|
||||
suppressions,
|
||||
);
|
||||
if noqa.is_enabled() {
|
||||
for index in ignored.iter().rev() {
|
||||
@@ -403,9 +400,6 @@ pub fn add_noqa_to_path(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics, ignoring any existing `noqa` directives.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -420,7 +414,6 @@ pub fn add_noqa_to_path(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
// Add any missing `# noqa` pragmas.
|
||||
@@ -434,7 +427,6 @@ pub fn add_noqa_to_path(
|
||||
&directives.noqa_line_for,
|
||||
stylist.line_ending(),
|
||||
reason,
|
||||
&suppressions,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -469,9 +461,6 @@ pub fn lint_only(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -486,7 +475,6 @@ pub fn lint_only(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
LinterResult {
|
||||
@@ -578,9 +566,6 @@ pub fn lint_fix<'a>(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -595,7 +580,6 @@ pub fn lint_fix<'a>(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
@@ -785,7 +769,6 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
||||
use crate::{Locator, assert_diagnostics, directives, settings};
|
||||
|
||||
@@ -961,7 +944,6 @@ mod tests {
|
||||
&locator,
|
||||
&indexer,
|
||||
);
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
let mut diagnostics = check_path(
|
||||
path,
|
||||
None,
|
||||
@@ -975,7 +957,6 @@ mod tests {
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
||||
diagnostics
|
||||
@@ -1001,7 +982,6 @@ mod tests {
|
||||
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
|
||||
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
|
||||
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
|
||||
#[test_case(Path::new("annotated_global.py"), PythonVersion::PY314)]
|
||||
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"semantic_syntax_error_{}_{}",
|
||||
|
||||
@@ -197,7 +197,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
f,
|
||||
"{fix}{body}",
|
||||
fix = format_args!("[{}] ", "*".cyan()),
|
||||
body = self.message.concise_message(),
|
||||
body = self.message.body(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -208,14 +208,14 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = code.red().bold(),
|
||||
body = self.message.concise_message(),
|
||||
body = self.message.body(),
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{code}: {body}",
|
||||
code = self.message.id().as_str().red().bold(),
|
||||
body = self.message.concise_message(),
|
||||
body = self.message.body(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ impl<'a> SarifResult<'a> {
|
||||
rule_id: RuleCode::from(diagnostic),
|
||||
level: "error".to_string(),
|
||||
message: SarifMessage {
|
||||
text: diagnostic.concise_message().to_string(),
|
||||
text: diagnostic.body().to_string(),
|
||||
},
|
||||
fixes: Self::fix(diagnostic, &uri).into_iter().collect(),
|
||||
locations: vec![SarifLocation {
|
||||
|
||||
@@ -20,14 +20,12 @@ use crate::Locator;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::suppression::Suppressions;
|
||||
|
||||
/// Generates an array of edits that matches the length of `messages`.
|
||||
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
||||
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
||||
/// the diagnostic. These edits may conflict with each other and should not be applied
|
||||
/// simultaneously.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn generate_noqa_edits(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
@@ -36,19 +34,11 @@ pub fn generate_noqa_edits(
|
||||
external: &[String],
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<Option<Edit>> {
|
||||
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
||||
let exemption = FileExemption::from(&file_directives);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||
let comments = find_noqa_comments(
|
||||
diagnostics,
|
||||
locator,
|
||||
&exemption,
|
||||
&directives,
|
||||
noqa_line_for,
|
||||
suppressions,
|
||||
);
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
||||
}
|
||||
|
||||
@@ -735,7 +725,6 @@ pub(crate) fn add_noqa(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
reason: Option<&str>,
|
||||
suppressions: &Suppressions,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
@@ -746,7 +735,6 @@ pub(crate) fn add_noqa(
|
||||
noqa_line_for,
|
||||
line_ending,
|
||||
reason,
|
||||
suppressions,
|
||||
);
|
||||
|
||||
fs::write(path, output)?;
|
||||
@@ -763,7 +751,6 @@ fn add_noqa_inner(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
reason: Option<&str>,
|
||||
suppressions: &Suppressions,
|
||||
) -> (usize, String) {
|
||||
let mut count = 0;
|
||||
|
||||
@@ -773,14 +760,7 @@ fn add_noqa_inner(
|
||||
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||
|
||||
let comments = find_noqa_comments(
|
||||
diagnostics,
|
||||
locator,
|
||||
&exemption,
|
||||
&directives,
|
||||
noqa_line_for,
|
||||
suppressions,
|
||||
);
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
|
||||
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
||||
|
||||
@@ -879,7 +859,6 @@ fn find_noqa_comments<'a>(
|
||||
exemption: &'a FileExemption,
|
||||
directives: &'a NoqaDirectives,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
suppressions: &'a Suppressions,
|
||||
) -> Vec<Option<NoqaComment<'a>>> {
|
||||
// List of noqa comments, ordered to match up with `messages`
|
||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||
@@ -896,12 +875,6 @@ fn find_noqa_comments<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply ranged suppressions next
|
||||
if suppressions.check_diagnostic(message) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
if let Some(parent) = message.parent() {
|
||||
if let Some(directive_line) =
|
||||
@@ -1280,7 +1253,6 @@ mod tests {
|
||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Edit, Violation};
|
||||
use crate::{Locator, generate_noqa_edits};
|
||||
|
||||
@@ -2876,7 +2848,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, format!("{contents}"));
|
||||
@@ -2901,7 +2872,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: F841\n");
|
||||
@@ -2933,7 +2903,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
||||
@@ -2965,7 +2934,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, "x = 1 # noqa");
|
||||
@@ -2988,7 +2956,6 @@ print(
|
||||
let messages = [PrintfStringFormatting
|
||||
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let suppressions = Suppressions::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&messages,
|
||||
@@ -2997,7 +2964,6 @@ print(
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
&suppressions,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
@@ -3021,7 +2987,6 @@ bar =
|
||||
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let suppressions = Suppressions::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&messages,
|
||||
@@ -3030,7 +2995,6 @@ bar =
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
&suppressions,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
|
||||
@@ -9,11 +9,6 @@ use crate::settings::LinterSettings;
|
||||
|
||||
// Rule-specific behavior
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/21382
|
||||
pub(crate) const fn is_custom_exception_checking_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/15541
|
||||
pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
@@ -291,8 +286,3 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/21623
|
||||
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ static ALLOWLIST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
# Case-sensitive
|
||||
pyright
|
||||
| pyrefly
|
||||
| ruff\s*:\s*(disable|enable)
|
||||
| mypy:
|
||||
| type:\s*ignore
|
||||
| SPDX-License-Identifier:
|
||||
@@ -149,8 +148,6 @@ mod tests {
|
||||
assert!(!comment_contains_code("# 123", &[]));
|
||||
assert!(!comment_contains_code("# 123.1", &[]));
|
||||
assert!(!comment_contains_code("# 1, 2, 3", &[]));
|
||||
assert!(!comment_contains_code("# ruff: disable[E501]", &[]));
|
||||
assert!(!comment_contains_code("#ruff:enable[E501, F84]", &[]));
|
||||
assert!(!comment_contains_code(
|
||||
"# pylint: disable=redefined-outer-name",
|
||||
&[]
|
||||
|
||||
@@ -91,8 +91,8 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def:
|
||||
response_model_arg,
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
)
|
||||
.map(Fix::unsafe_edit)
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
|
||||
pub(crate) fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ mod async_zero_sleep;
|
||||
mod blocking_http_call;
|
||||
mod blocking_http_call_httpx;
|
||||
mod blocking_input;
|
||||
pub(crate) mod blocking_open_call;
|
||||
mod blocking_open_call;
|
||||
mod blocking_path_methods;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{checkers::ast::Checker, settings::LinterSettings};
|
||||
/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup].
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// [`markupsafe.Markup`][markupsafe-markup] does not perform any escaping, so passing dynamic
|
||||
/// [`markupsafe.Markup`] does not perform any escaping, so passing dynamic
|
||||
/// content, like f-strings, variables or interpolated strings will potentially
|
||||
/// lead to XSS vulnerabilities.
|
||||
///
|
||||
|
||||
@@ -75,7 +75,6 @@ pub(crate) fn unsafe_yaml_load(checker: &Checker, call: &ast::ExprCall) {
|
||||
qualified_name.segments(),
|
||||
["yaml", "SafeLoader" | "CSafeLoader"]
|
||||
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
||||
| ["yaml", "cyaml", "CSafeLoader"]
|
||||
)
|
||||
})
|
||||
{
|
||||
|
||||
@@ -74,7 +74,12 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker
|
||||
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument("strict=False", &call.arguments, checker.tokens()),
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::Write;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
||||
@@ -166,7 +166,12 @@ fn move_initialization(
|
||||
return None;
|
||||
}
|
||||
|
||||
let range = match parenthesized_range(default.into(), parameter.into(), checker.tokens()) {
|
||||
let range = match parenthesized_range(
|
||||
default.into(),
|
||||
parameter.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
Some(range) => range,
|
||||
None => default.range(),
|
||||
};
|
||||
@@ -189,8 +194,13 @@ fn move_initialization(
|
||||
"{} = {}",
|
||||
parameter.parameter.name(),
|
||||
locator.slice(
|
||||
parenthesized_range(default.into(), parameter.into(), checker.tokens())
|
||||
.unwrap_or(default.range())
|
||||
parenthesized_range(
|
||||
default.into(),
|
||||
parameter.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source()
|
||||
)
|
||||
.unwrap_or(default.range())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -92,7 +92,12 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
let mut diagnostic = checker.report_diagnostic(NoExplicitStacklevel, call.func.range());
|
||||
|
||||
let edit = add_argument("stacklevel=2", &call.arguments, checker.tokens());
|
||||
let edit = add_argument(
|
||||
"stacklevel=2",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||
}
|
||||
|
||||
@@ -70,7 +70,12 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker
|
||||
.report_diagnostic(ZipWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument("strict=False", &call.arguments, checker.tokens()),
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:242:20
|
||||
--> B006_B008.py:239:20
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
239 |
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
236 |
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
- def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
242 + def nested_combo(a=None):
|
||||
243 | pass
|
||||
244 |
|
||||
245 |
|
||||
239 + def nested_combo(a=None):
|
||||
240 | pass
|
||||
241 |
|
||||
242 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:279:27
|
||||
--> B006_B008.py:276:27
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
| ^^
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
276 |
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
273 |
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
- a: list[int] | None = [],
|
||||
279 + a: list[int] | None = None,
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 + a: list[int] | None = None,
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:280:35
|
||||
--> B006_B008.py:277:35
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
- b: Optional[Dict[int, int]] = {},
|
||||
280 + b: Optional[Dict[int, int]] = None,
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
277 + b: Optional[Dict[int, int]] = None,
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:281:62
|
||||
--> B006_B008.py:278:62
|
||||
|
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
284 | pass
|
||||
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
281 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:282:80
|
||||
--> B006_B008.py:279:80
|
||||
|
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
283 | ):
|
||||
284 | pass
|
||||
280 | ):
|
||||
281 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
283 | ):
|
||||
284 | pass
|
||||
285 |
|
||||
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
280 | ):
|
||||
281 | pass
|
||||
282 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:287:52
|
||||
--> B006_B008.py:284:52
|
||||
|
|
||||
287 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
288 | """Docstring"""
|
||||
285 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
284 | pass
|
||||
285 |
|
||||
281 | pass
|
||||
282 |
|
||||
283 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
287 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
287 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:291:52
|
||||
--> B006_B008.py:288:52
|
||||
|
|
||||
291 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
288 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
287 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
291 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
294 |
|
||||
288 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
291 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:296:52
|
||||
--> B006_B008.py:293:52
|
||||
|
|
||||
296 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
297 | """Docstring"""; ...
|
||||
294 | """Docstring"""; ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
293 | ...
|
||||
294 |
|
||||
290 | ...
|
||||
291 |
|
||||
292 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
296 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
296 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:300:52
|
||||
--> B006_B008.py:297:52
|
||||
|
|
||||
300 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
297 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
296 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
300 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
303 |
|
||||
297 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
300 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:305:52
|
||||
--> B006_B008.py:302:52
|
||||
|
|
||||
305 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
302 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
| ____________________________________________________^
|
||||
306 | | # This is a comment
|
||||
307 | | }):
|
||||
303 | | # This is a comment
|
||||
304 | | }):
|
||||
| |_^
|
||||
308 | """Docstring"""
|
||||
305 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
302 | ...
|
||||
303 |
|
||||
304 |
|
||||
299 | ...
|
||||
300 |
|
||||
301 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {
|
||||
- # This is a comment
|
||||
- }):
|
||||
305 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
306 | """Docstring"""
|
||||
307 |
|
||||
308 |
|
||||
302 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
303 | """Docstring"""
|
||||
304 |
|
||||
305 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:311:52
|
||||
--> B006_B008.py:308:52
|
||||
|
|
||||
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
| ^^
|
||||
312 | : \
|
||||
313 | """Docstring"""
|
||||
309 | : \
|
||||
310 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:316:52
|
||||
--> B006_B008.py:313:52
|
||||
|
|
||||
316 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
313 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
317 | """Docstring without newline"""
|
||||
314 | """Docstring without newline"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
313 | """Docstring"""
|
||||
314 |
|
||||
315 |
|
||||
310 | """Docstring"""
|
||||
311 |
|
||||
312 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
316 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
317 | """Docstring without newline"""
|
||||
313 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
314 | """Docstring without newline"""
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -53,39 +53,39 @@ B008 Do not perform function call in argument defaults; instead, perform the cal
|
||||
|
|
||||
|
||||
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:242:31
|
||||
--> B006_B008.py:239:31
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:248:22
|
||||
--> B006_B008.py:245:22
|
||||
|
|
||||
246 | # Don't flag nested B006 since we can't guarantee that
|
||||
247 | # it isn't made mutable by the outer operation.
|
||||
248 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
243 | # Don't flag nested B006 since we can't guarantee that
|
||||
244 | # it isn't made mutable by the outer operation.
|
||||
245 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
249 | pass
|
||||
246 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:253:19
|
||||
--> B006_B008.py:250:19
|
||||
|
|
||||
252 | # B008-ception.
|
||||
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
254 | pass
|
||||
251 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:253:37
|
||||
--> B006_B008.py:250:37
|
||||
|
|
||||
252 | # B008-ception.
|
||||
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
254 | pass
|
||||
251 | pass
|
||||
|
|
||||
|
||||
@@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:242:20
|
||||
--> B006_B008.py:239:20
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
239 |
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
236 |
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
- def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
242 + def nested_combo(a=None):
|
||||
243 | pass
|
||||
244 |
|
||||
245 |
|
||||
239 + def nested_combo(a=None):
|
||||
240 | pass
|
||||
241 |
|
||||
242 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:279:27
|
||||
--> B006_B008.py:276:27
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
| ^^
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
276 |
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
273 |
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
- a: list[int] | None = [],
|
||||
279 + a: list[int] | None = None,
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 + a: list[int] | None = None,
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:280:35
|
||||
--> B006_B008.py:277:35
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
- b: Optional[Dict[int, int]] = {},
|
||||
280 + b: Optional[Dict[int, int]] = None,
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
277 + b: Optional[Dict[int, int]] = None,
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:281:62
|
||||
--> B006_B008.py:278:62
|
||||
|
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
284 | pass
|
||||
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
281 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:282:80
|
||||
--> B006_B008.py:279:80
|
||||
|
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
283 | ):
|
||||
284 | pass
|
||||
280 | ):
|
||||
281 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
283 | ):
|
||||
284 | pass
|
||||
285 |
|
||||
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
280 | ):
|
||||
281 | pass
|
||||
282 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:287:52
|
||||
--> B006_B008.py:284:52
|
||||
|
|
||||
287 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
288 | """Docstring"""
|
||||
285 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
284 | pass
|
||||
285 |
|
||||
281 | pass
|
||||
282 |
|
||||
283 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
287 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
287 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:291:52
|
||||
--> B006_B008.py:288:52
|
||||
|
|
||||
291 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
288 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
287 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
291 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
294 |
|
||||
288 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
291 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:296:52
|
||||
--> B006_B008.py:293:52
|
||||
|
|
||||
296 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
297 | """Docstring"""; ...
|
||||
294 | """Docstring"""; ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
293 | ...
|
||||
294 |
|
||||
290 | ...
|
||||
291 |
|
||||
292 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
296 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
296 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:300:52
|
||||
--> B006_B008.py:297:52
|
||||
|
|
||||
300 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
297 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
296 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
300 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
303 |
|
||||
297 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
300 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:305:52
|
||||
--> B006_B008.py:302:52
|
||||
|
|
||||
305 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
302 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
| ____________________________________________________^
|
||||
306 | | # This is a comment
|
||||
307 | | }):
|
||||
303 | | # This is a comment
|
||||
304 | | }):
|
||||
| |_^
|
||||
308 | """Docstring"""
|
||||
305 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
302 | ...
|
||||
303 |
|
||||
304 |
|
||||
299 | ...
|
||||
300 |
|
||||
301 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {
|
||||
- # This is a comment
|
||||
- }):
|
||||
305 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
306 | """Docstring"""
|
||||
307 |
|
||||
308 |
|
||||
302 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
303 | """Docstring"""
|
||||
304 |
|
||||
305 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:311:52
|
||||
--> B006_B008.py:308:52
|
||||
|
|
||||
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
| ^^
|
||||
312 | : \
|
||||
313 | """Docstring"""
|
||||
309 | : \
|
||||
310 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:316:52
|
||||
--> B006_B008.py:313:52
|
||||
|
|
||||
316 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
313 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
317 | """Docstring without newline"""
|
||||
314 | """Docstring without newline"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
313 | """Docstring"""
|
||||
314 |
|
||||
315 |
|
||||
310 | """Docstring"""
|
||||
311 |
|
||||
312 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
316 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
317 | """Docstring without newline"""
|
||||
313 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
314 | """Docstring without newline"""
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -2,8 +2,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
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_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -142,9 +142,13 @@ pub(crate) fn unnecessary_generator_list(checker: &Checker, call: &ast::ExprCall
|
||||
if *parenthesized {
|
||||
// The generator's range will include the innermost parentheses, but it could be
|
||||
// surrounded by additional parentheses.
|
||||
let range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or(argument.range());
|
||||
let range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(argument.range());
|
||||
|
||||
// The generator always parenthesizes the expression; trim the parentheses.
|
||||
let generator = checker.generator().expr(argument);
|
||||
|
||||
@@ -2,8 +2,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
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_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -147,9 +147,13 @@ pub(crate) fn unnecessary_generator_set(checker: &Checker, call: &ast::ExprCall)
|
||||
if *parenthesized {
|
||||
// The generator's range will include the innermost parentheses, but it could be
|
||||
// surrounded by additional parentheses.
|
||||
let range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or(argument.range());
|
||||
let range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(argument.range());
|
||||
|
||||
// The generator always parenthesizes the expression; trim the parentheses.
|
||||
let generator = checker.generator().expr(argument);
|
||||
|
||||
@@ -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_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -89,9 +89,13 @@ pub(crate) fn unnecessary_list_comprehension_set(checker: &Checker, call: &ast::
|
||||
|
||||
// If the list comprehension is parenthesized, remove the parentheses in addition to
|
||||
// removing the brackets.
|
||||
let replacement_range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or_else(|| argument.range());
|
||||
let replacement_range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or_else(|| argument.range());
|
||||
|
||||
let span = argument.range().add_start(one).sub_end(one);
|
||||
let replacement =
|
||||
|
||||
@@ -32,10 +32,6 @@ mod tests {
|
||||
Path::new("ISC_syntax_error_2.py")
|
||||
)]
|
||||
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"))]
|
||||
#[test_case(
|
||||
Rule::ImplicitStringConcatenationInCollectionLiteral,
|
||||
Path::new("ISC004.py")
|
||||
)]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{Expr, StringLike};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for implicitly concatenated strings inside list, tuple, and set literals.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In collection literals, implicit string concatenation is often the result of
|
||||
/// a missing comma between elements, which can silently merge items together.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// facts = (
|
||||
/// "Lobsters have blue blood.",
|
||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
/// "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Instead, you likely intended:
|
||||
/// ```python
|
||||
/// facts = (
|
||||
/// "Lobsters have blue blood.",
|
||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree.",
|
||||
/// "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// If the concatenation is intentional, wrap it in parentheses to make it
|
||||
/// explicit:
|
||||
/// ```python
|
||||
/// facts = (
|
||||
/// "Lobsters have blue blood.",
|
||||
/// "The liver is the only human organ that can fully regenerate itself.",
|
||||
/// (
|
||||
/// "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
/// "In 1971, astronaut Alan Shepard played golf on the moon."
|
||||
/// ),
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// The fix is safe in that it does not change the semantics of your code.
|
||||
/// However, the issue is that you may often want to change semantics
|
||||
/// by adding a missing comma.
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "0.14.10")]
|
||||
pub(crate) struct ImplicitStringConcatenationInCollectionLiteral;
|
||||
|
||||
impl Violation for ImplicitStringConcatenationInCollectionLiteral {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Unparenthesized implicit string concatenation in collection".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Wrap implicitly concatenated strings in parentheses".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// ISC004
|
||||
pub(crate) fn implicit_string_concatenation_in_collection_literal(
|
||||
checker: &Checker,
|
||||
expr: &Expr,
|
||||
elements: &[Expr],
|
||||
) {
|
||||
for element in elements {
|
||||
let Ok(string_like) = StringLike::try_from(element) else {
|
||||
continue;
|
||||
};
|
||||
if !string_like.is_implicit_concatenated() {
|
||||
continue;
|
||||
}
|
||||
if parenthesized_range(
|
||||
string_like.as_expression_ref(),
|
||||
expr.into(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
ImplicitStringConcatenationInCollectionLiteral,
|
||||
string_like.range(),
|
||||
);
|
||||
diagnostic.help("Did you forget a comma?");
|
||||
diagnostic.set_fix(Fix::unsafe_edits(
|
||||
Edit::insertion("(".to_string(), string_like.range().start()),
|
||||
[Edit::insertion(")".to_string(), string_like.range().end())],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_source_file::LineRanges;
|
||||
@@ -88,7 +88,13 @@ pub(crate) fn explicit(checker: &Checker, expr: &Expr) {
|
||||
checker.report_diagnostic(ExplicitStringConcatenation, expr.range());
|
||||
|
||||
let is_parenthesized = |expr: &Expr| {
|
||||
parenthesized_range(expr.into(), bin_op.into(), checker.tokens()).is_some()
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
bin_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.is_some()
|
||||
};
|
||||
// If either `left` or `right` is parenthesized, generating
|
||||
// a fix would be too involved. Just report the diagnostic.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
pub(crate) use collection_literal::*;
|
||||
pub(crate) use explicit::*;
|
||||
pub(crate) use implicit::*;
|
||||
|
||||
mod collection_literal;
|
||||
mod explicit;
|
||||
mod implicit;
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
|
||||
---
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:4:5
|
||||
|
|
||||
2 | "Lobsters have blue blood.",
|
||||
3 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
4 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
5 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
| |______________________________________________________________^
|
||||
6 | )
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
1 | facts = (
|
||||
2 | "Lobsters have blue blood.",
|
||||
3 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
4 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
5 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
||||
6 | )
|
||||
7 |
|
||||
8 | facts = [
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:11:5
|
||||
|
|
||||
9 | "Lobsters have blue blood.",
|
||||
10 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
11 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
12 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
| |______________________________________________________________^
|
||||
13 | ]
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
8 | facts = [
|
||||
9 | "Lobsters have blue blood.",
|
||||
10 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
11 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
12 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
||||
13 | ]
|
||||
14 |
|
||||
15 | facts = {
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:18:5
|
||||
|
|
||||
16 | "Lobsters have blue blood.",
|
||||
17 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
18 | / "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
19 | | "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
| |______________________________________________________________^
|
||||
20 | }
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
15 | facts = {
|
||||
16 | "Lobsters have blue blood.",
|
||||
17 | "The liver is the only human organ that can fully regenerate itself.",
|
||||
- "Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
- "In 1971, astronaut Alan Shepard played golf on the moon.",
|
||||
18 + ("Clarinets are made almost entirely out of wood from the mpingo tree."
|
||||
19 + "In 1971, astronaut Alan Shepard played golf on the moon."),
|
||||
20 | }
|
||||
21 |
|
||||
22 | facts = {
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:30:5
|
||||
|
|
||||
29 | facts = (
|
||||
30 | / "Octopuses have three hearts."
|
||||
31 | | # Missing comma here.
|
||||
32 | | "Honey never spoils.",
|
||||
| |_________________________^
|
||||
33 | )
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
27 | }
|
||||
28 |
|
||||
29 | facts = (
|
||||
- "Octopuses have three hearts."
|
||||
30 + ("Octopuses have three hearts."
|
||||
31 | # Missing comma here.
|
||||
- "Honey never spoils.",
|
||||
32 + "Honey never spoils."),
|
||||
33 | )
|
||||
34 |
|
||||
35 | facts = [
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:36:5
|
||||
|
|
||||
35 | facts = [
|
||||
36 | / "Octopuses have three hearts."
|
||||
37 | | # Missing comma here.
|
||||
38 | | "Honey never spoils.",
|
||||
| |_________________________^
|
||||
39 | ]
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
33 | )
|
||||
34 |
|
||||
35 | facts = [
|
||||
- "Octopuses have three hearts."
|
||||
36 + ("Octopuses have three hearts."
|
||||
37 | # Missing comma here.
|
||||
- "Honey never spoils.",
|
||||
38 + "Honey never spoils."),
|
||||
39 | ]
|
||||
40 |
|
||||
41 | facts = {
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
ISC004 [*] Unparenthesized implicit string concatenation in collection
|
||||
--> ISC004.py:42:5
|
||||
|
|
||||
41 | facts = {
|
||||
42 | / "Octopuses have three hearts."
|
||||
43 | | # Missing comma here.
|
||||
44 | | "Honey never spoils.",
|
||||
| |_________________________^
|
||||
45 | }
|
||||
|
|
||||
help: Wrap implicitly concatenated strings in parentheses
|
||||
help: Did you forget a comma?
|
||||
39 | ]
|
||||
40 |
|
||||
41 | facts = {
|
||||
- "Octopuses have three hearts."
|
||||
42 + ("Octopuses have three hearts."
|
||||
43 | # Missing comma here.
|
||||
- "Honey never spoils.",
|
||||
44 + "Honey never spoils."),
|
||||
45 | }
|
||||
46 |
|
||||
47 | facts = (
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -111,6 +111,7 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
|
||||
}
|
||||
|
||||
let arguments = &call.arguments;
|
||||
let source = checker.source();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(ExcInfoOutsideExceptHandler, exc_info.range);
|
||||
|
||||
@@ -119,8 +120,8 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
|
||||
exc_info,
|
||||
arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
source,
|
||||
checker.comment_ranges(),
|
||||
)?;
|
||||
Ok(Fix::unsafe_edit(edit))
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user