Compare commits
56 Commits
0.12.5
...
david/enum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e935bc5578 | ||
|
|
81867ea7ce | ||
|
|
a54061e757 | ||
|
|
19569bf838 | ||
|
|
e0f4f25d28 | ||
|
|
c6a123290d | ||
|
|
d4f64cd474 | ||
|
|
e4f64480da | ||
|
|
4016aff057 | ||
|
|
24134837f3 | ||
|
|
130d4e1135 | ||
|
|
e63dfa3d18 | ||
|
|
6d0f3ef3a5 | ||
|
|
201b079084 | ||
|
|
2680f2ed81 | ||
|
|
afdfa042f3 | ||
|
|
8c0743df97 | ||
|
|
13634ff433 | ||
|
|
7a541f597f | ||
|
|
2deb50f4e3 | ||
|
|
85e22645aa | ||
|
|
d3f6de8b0e | ||
|
|
9eb8174209 | ||
|
|
9c68616d91 | ||
|
|
9280c7e945 | ||
|
|
e19145040f | ||
|
|
ef3a195f28 | ||
|
|
008bbfdf5a | ||
|
|
df5eba7583 | ||
|
|
469c50b0b7 | ||
|
|
738246627f | ||
|
|
e867830848 | ||
|
|
72fdb7d439 | ||
|
|
fbf1dfc782 | ||
|
|
a0d8ff51dd | ||
|
|
165091a31c | ||
|
|
4a4dc38b5b | ||
|
|
3e366fdf13 | ||
|
|
53e9e4421c | ||
|
|
859262bd49 | ||
|
|
c0768dfd96 | ||
|
|
d4eb4277ad | ||
|
|
b033fb6bfd | ||
|
|
f722bfa9e6 | ||
|
|
b124e182ca | ||
|
|
57373a7e4d | ||
|
|
ae9d450b5f | ||
|
|
c8c80e054e | ||
|
|
4bc34b82ef | ||
|
|
d9cab4d242 | ||
|
|
d77b7312b0 | ||
|
|
f9091ea8bb | ||
|
|
1d2181623c | ||
|
|
dc6be457b5 | ||
|
|
1079975b35 | ||
|
|
39eb0f6c6c |
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -429,7 +429,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -451,7 +451,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -652,7 +652,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -682,7 +682,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
- uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -722,7 +722,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -765,7 +765,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -897,7 +897,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -911,7 +911,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
@@ -930,7 +930,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -944,7 +944,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
109
.github/workflows/typing_conformance.yaml
vendored
Normal file
109
.github/workflows/typing_conformance.yaml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Run typing conformance
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
- ".github/workflows/typing_conformance.yaml"
|
||||
- ".github/workflows/typing_conformance_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
typing_conformance:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: python/typing
|
||||
ref: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
|
||||
path: typing
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Compute diagnostic diff
|
||||
shell: bash
|
||||
run: |
|
||||
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
|
||||
|
||||
# Build the executable for the old and new commit
|
||||
(
|
||||
cd ruff
|
||||
|
||||
echo "new commit"
|
||||
git checkout -b new_commit "${{ github.event.pull_request.head.sha }}"
|
||||
git rev-list --format=%s --max-count=1 new_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-new
|
||||
|
||||
echo "old commit (merge base)"
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b old_commit "$MERGE_BASE"
|
||||
git rev-list --format=%s --max-count=1 old_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-old
|
||||
)
|
||||
|
||||
(
|
||||
cd typing/conformance/tests
|
||||
|
||||
echo "Running ty on old commit (merge base)"
|
||||
"$RUFF_DIR/ty-old" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/old-output.txt" 2>&1 || true
|
||||
|
||||
echo "Running ty on new commit"
|
||||
"$RUFF_DIR/ty-new" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/new-output.txt" 2>&1 || true
|
||||
)
|
||||
|
||||
if ! diff -u old-output.txt new-output.txt > typing_conformance_diagnostics.diff; then
|
||||
echo "Differences found between base and PR"
|
||||
else
|
||||
echo "No differences found"
|
||||
touch typing_conformance_diagnostics.diff
|
||||
fi
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: typing_conformance_diagnostics_diff
|
||||
path: typing_conformance_diagnostics.diff
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
97
.github/workflows/typing_conformance_comment.yaml
vendored
Normal file
97
.github/workflows/typing_conformance_comment.yaml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: PR comment (typing_conformance)
|
||||
|
||||
on: # zizmor: ignore[dangerous-triggers]
|
||||
workflow_run:
|
||||
workflows: [Run typing conformance]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_run_id:
|
||||
description: The typing_conformance workflow that triggers the workflow run
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download PR number
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Parse pull request number
|
||||
id: pr-number
|
||||
run: |
|
||||
if [[ -f pr-number ]]
|
||||
then
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download typing_conformance results"
|
||||
id: download-typing_conformance_diff
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
with:
|
||||
name: typing_conformance_diagnostics_diff
|
||||
workflow: typing_conformance.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/typing_conformance_diagnostics_diff
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Generate comment content
|
||||
id: generate-comment
|
||||
if: ${{ steps.download-typing_conformance_diff.outputs.found_artifact == 'true' }}
|
||||
run: |
|
||||
# Guard against malicious typing_conformance results that symlink to a secret
|
||||
# file on this runner
|
||||
if [[ -L pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff ]]
|
||||
then
|
||||
echo "Error: typing_conformance_diagnostics.diff cannot be a symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Note this identifier is used to find the comment to update on
|
||||
# subsequent runs
|
||||
echo '<!-- generated-comment typing_conformance_diagnostics_diff -->' >> comment.txt
|
||||
|
||||
echo '## Diagnostic diff on typing conformance tests' >> comment.txt
|
||||
if [ -s "pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff" ]; then
|
||||
echo '<details>' >> comment.txt
|
||||
echo '<summary>Changes were detected when running ty on typing conformance tests</summary>' >> comment.txt
|
||||
echo '' >> comment.txt
|
||||
echo '```diff' >> comment.txt
|
||||
cat pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff >> comment.txt
|
||||
echo '```' >> comment.txt
|
||||
echo '</details>' >> comment.txt
|
||||
else
|
||||
echo 'No changes detected when running ty on typing conformance tests ✅' >> comment.txt
|
||||
fi
|
||||
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.txt >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "<!-- generated-comment typing_conformance_diagnostics_diff -->"
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
body-path: comment.txt
|
||||
edit-mode: replace
|
||||
@@ -81,10 +81,10 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.4
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
- id: ruff-check
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
|
||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -530,7 +530,7 @@ dependencies = [
|
||||
"ciborium",
|
||||
"clap",
|
||||
"codspeed",
|
||||
"criterion-plot",
|
||||
"criterion-plot 0.5.0",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
@@ -713,15 +713,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
|
||||
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"criterion-plot 0.6.0",
|
||||
"itertools 0.13.0",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
@@ -742,6 +742,16 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
@@ -1151,9 +1161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
|
||||
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1162,9 +1172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
|
||||
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
@@ -2881,6 +2891,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_static",
|
||||
"unicode-width 0.2.1",
|
||||
"web-time",
|
||||
"zip",
|
||||
]
|
||||
@@ -2960,6 +2971,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"memchr",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_linter",
|
||||
@@ -4304,7 +4316,6 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thin-vec",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
|
||||
@@ -73,7 +73,7 @@ console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.6.0", default-features = false }
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
@@ -83,7 +83,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.5.0", features = [
|
||||
get-size2 = { version = "0.6.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -166,7 +166,6 @@ strum_macros = { version = "0.27.0" }
|
||||
syn = { version = "2.0.55" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thin-vec = { version = "0.2.14" }
|
||||
thiserror = { version = "2.0.0" }
|
||||
tikv-jemallocator = { version = "0.6.0" }
|
||||
toml = { version = "0.9.0" }
|
||||
|
||||
@@ -169,6 +169,9 @@ pub struct AnalyzeGraphCommand {
|
||||
/// Attempt to detect imports from string literals.
|
||||
#[clap(long)]
|
||||
detect_string_imports: bool,
|
||||
/// The minimum number of dots in a string import to consider it a valid import.
|
||||
#[clap(long)]
|
||||
min_dots: Option<usize>,
|
||||
/// Enable preview mode. Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
@@ -808,6 +811,7 @@ impl AnalyzeGraphCommand {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
string_imports_min_dots: self.min_dots,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
target_version: self.target_version.map(ast::PythonVersion::from),
|
||||
..ExplicitConfigOverrides::default()
|
||||
@@ -1305,6 +1309,7 @@ struct ExplicitConfigOverrides {
|
||||
show_fixes: Option<bool>,
|
||||
extension: Option<Vec<ExtensionPair>>,
|
||||
detect_string_imports: Option<bool>,
|
||||
string_imports_min_dots: Option<usize>,
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
@@ -1392,6 +1397,9 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
if let Some(detect_string_imports) = &self.detect_string_imports {
|
||||
config.analyze.detect_string_imports = Some(*detect_string_imports);
|
||||
}
|
||||
if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
|
||||
config.analyze.string_imports_min_dots = Some(*string_imports_min_dots);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ pub(crate) fn analyze_graph(
|
||||
|
||||
// Resolve the per-file settings.
|
||||
let settings = resolver.resolve(path);
|
||||
let string_imports = settings.analyze.detect_string_imports;
|
||||
let string_imports = settings.analyze.string_imports;
|
||||
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
|
||||
|
||||
// Skip excluded files.
|
||||
|
||||
@@ -279,6 +279,7 @@ mod test {
|
||||
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_color(false)
|
||||
.emit(
|
||||
&mut output,
|
||||
&diagnostics.inner,
|
||||
|
||||
@@ -57,33 +57,40 @@ fn dependencies() -> Result<()> {
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f(): pass
|
||||
"#})?;
|
||||
root.child("ruff")
|
||||
.child("e.pyi")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f() -> None: ...
|
||||
"#})?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py"
|
||||
],
|
||||
"ruff/e.py": []
|
||||
}
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py",
|
||||
"ruff/e.pyi"
|
||||
],
|
||||
"ruff/e.py": [],
|
||||
"ruff/e.pyi": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -197,23 +204,43 @@ fn string_detection() -> Result<()> {
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().arg("--detect-string-imports").current_dir(&root), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": []
|
||||
}
|
||||
assert_cmd_snapshot!(command().arg("--detect-string-imports").current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [],
|
||||
"ruff/c.py": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().arg("--detect-string-imports").arg("--min-dots").arg("1").current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2422,7 +2422,7 @@ requires-python = ">= 3.11"
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.11
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -2734,7 +2734,7 @@ requires-python = ">= 3.11"
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.10
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -3098,7 +3098,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.11
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -3478,7 +3478,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.11
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -3806,7 +3806,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.10
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -4134,7 +4134,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.9
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -4419,7 +4419,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.9
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -4757,7 +4757,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.10
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@ formatter.docstring_code_line_width = dynamic
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.7
|
||||
analyze.detect_string_imports = false
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
unicode-width = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
|
||||
@@ -6,7 +6,9 @@ use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
};
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -7,7 +8,7 @@ use ruff_annotate_snippets::{
|
||||
};
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
|
||||
use crate::{
|
||||
@@ -520,7 +521,7 @@ impl<'r> RenderableSnippets<'r> {
|
||||
#[derive(Debug)]
|
||||
struct RenderableSnippet<'r> {
|
||||
/// The actual snippet text.
|
||||
snippet: &'r str,
|
||||
snippet: Cow<'r, str>,
|
||||
/// The absolute line number corresponding to where this
|
||||
/// snippet begins.
|
||||
line_start: OneIndexed,
|
||||
@@ -580,6 +581,13 @@ impl<'r> RenderableSnippet<'r> {
|
||||
.iter()
|
||||
.map(|ann| RenderableAnnotation::new(snippet_start, ann))
|
||||
.collect();
|
||||
|
||||
let EscapedSourceCode {
|
||||
text: snippet,
|
||||
annotations,
|
||||
} = replace_whitespace_and_unprintable(snippet, annotations)
|
||||
.fix_up_empty_spans_after_line_terminator();
|
||||
|
||||
RenderableSnippet {
|
||||
snippet,
|
||||
line_start,
|
||||
@@ -590,7 +598,7 @@ impl<'r> RenderableSnippet<'r> {
|
||||
|
||||
/// Convert this to an "annotate" snippet.
|
||||
fn to_annotate<'a>(&'a self, path: &'a str) -> AnnotateSnippet<'a> {
|
||||
AnnotateSnippet::source(self.snippet)
|
||||
AnnotateSnippet::source(&self.snippet)
|
||||
.origin(path)
|
||||
.line_start(self.line_start.get())
|
||||
.annotations(
|
||||
@@ -820,6 +828,230 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
path
|
||||
}
|
||||
|
||||
/// Given some source code and annotation ranges, this routine replaces tabs
|
||||
/// with ASCII whitespace, and unprintable characters with printable
|
||||
/// representations of them.
|
||||
///
|
||||
/// The source code and annotations returned are updated to reflect changes made
|
||||
/// to the source code (if any).
|
||||
fn replace_whitespace_and_unprintable<'r>(
|
||||
source: &'r str,
|
||||
mut annotations: Vec<RenderableAnnotation<'r>>,
|
||||
) -> EscapedSourceCode<'r> {
|
||||
// Updates the annotation ranges given by the caller whenever a single byte (at `index` in
|
||||
// `source`) is replaced with `len` bytes.
|
||||
//
|
||||
// When the index occurs before the start of the range, the range is
|
||||
// offset by `len`. When the range occurs after or at the start but before
|
||||
// the end, then the end of the range only is offset by `len`.
|
||||
let mut update_ranges = |index: usize, len: u32| {
|
||||
for ann in &mut annotations {
|
||||
if index < usize::from(ann.range.start()) {
|
||||
ann.range += TextSize::new(len - 1);
|
||||
} else if index < usize::from(ann.range.end()) {
|
||||
ann.range = ann.range.add_end(TextSize::new(len - 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If `c` is an unprintable character, then this returns a printable
|
||||
// representation of it (using a fancier Unicode codepoint).
|
||||
let unprintable_replacement = |c: char| -> Option<char> {
|
||||
match c {
|
||||
'\x07' => Some('␇'),
|
||||
'\x08' => Some('␈'),
|
||||
'\x1b' => Some('␛'),
|
||||
'\x7f' => Some('␡'),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
const TAB_SIZE: usize = 4;
|
||||
let mut width = 0;
|
||||
let mut column = 0;
|
||||
let mut last_end = 0;
|
||||
let mut result = String::new();
|
||||
for (index, c) in source.char_indices() {
|
||||
let old_width = width;
|
||||
match c {
|
||||
'\n' | '\r' => {
|
||||
width = 0;
|
||||
column = 0;
|
||||
}
|
||||
'\t' => {
|
||||
let tab_offset = TAB_SIZE - (column % TAB_SIZE);
|
||||
width += tab_offset;
|
||||
column += tab_offset;
|
||||
|
||||
let tab_width =
|
||||
u32::try_from(width - old_width).expect("small width because of tab size");
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
update_ranges(result.text_len().to_usize(), tab_width);
|
||||
|
||||
for _ in 0..tab_width {
|
||||
result.push(' ');
|
||||
}
|
||||
last_end = index + 1;
|
||||
}
|
||||
_ => {
|
||||
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
column += 1;
|
||||
|
||||
if let Some(printable) = unprintable_replacement(c) {
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
let len = printable.text_len().to_u32();
|
||||
update_ranges(result.text_len().to_usize(), len);
|
||||
|
||||
result.push(printable);
|
||||
last_end = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No tabs or unprintable chars
|
||||
if result.is_empty() {
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Borrowed(source),
|
||||
}
|
||||
} else {
|
||||
result.push_str(&source[last_end..]);
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Owned(result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EscapedSourceCode<'r> {
|
||||
text: Cow<'r, str>,
|
||||
annotations: Vec<RenderableAnnotation<'r>>,
|
||||
}
|
||||
|
||||
impl<'r> EscapedSourceCode<'r> {
|
||||
// This attempts to "fix up" the spans on each annotation in the case where
|
||||
// it's an empty span immediately following a line terminator.
|
||||
//
|
||||
// At present, `annotate-snippets` (both upstream and our vendored copy)
|
||||
// will render annotations of such spans to point to the space immediately
|
||||
// following the previous line. But ideally, this should point to the space
|
||||
// immediately preceding the next line.
|
||||
//
|
||||
// After attempting to fix `annotate-snippets` and giving up after a couple
|
||||
// hours, this routine takes a different tact: it adjusts the span to be
|
||||
// non-empty and it will cover the first codepoint of the following line.
|
||||
// This forces `annotate-snippets` to point to the right place.
|
||||
//
|
||||
// See also: <https://github.com/astral-sh/ruff/issues/15509> and
|
||||
// `ruff_linter::message::text::SourceCode::fix_up_empty_spans_after_line_terminator`,
|
||||
// from which this was adapted.
|
||||
fn fix_up_empty_spans_after_line_terminator(mut self) -> EscapedSourceCode<'r> {
|
||||
for ann in &mut self.annotations {
|
||||
let range = ann.range;
|
||||
if !range.is_empty()
|
||||
|| range.start() == TextSize::from(0)
|
||||
|| range.start() >= self.text.text_len()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !matches!(
|
||||
self.text.as_bytes()[range.start().to_usize() - 1],
|
||||
b'\n' | b'\r'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let start = range.start();
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
ann.range = TextRange::new(start, end);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
///
|
||||
/// let source = "❤️🧡💛💚💙💜";
|
||||
/// assert_eq!(source.text_len(), TextSize::from(26));
|
||||
/// assert!(!source.is_char_boundary(13));
|
||||
///
|
||||
/// let closest = ceil_char_boundary(source, TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&source[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
///
|
||||
/// let source = "Hello";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let source = "α";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(text.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| text.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -2359,7 +2591,7 @@ watermelon
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing diagnostics.
|
||||
fn builder(
|
||||
pub(super) fn builder(
|
||||
&mut self,
|
||||
identifier: &'static str,
|
||||
severity: Severity,
|
||||
@@ -2426,7 +2658,7 @@ watermelon
|
||||
///
|
||||
/// See the docs on `TestEnvironment::span` for the meaning of
|
||||
/// `path`, `line_offset_start` and `line_offset_end`.
|
||||
fn primary(
|
||||
pub(super) fn primary(
|
||||
mut self,
|
||||
path: &str,
|
||||
line_offset_start: &str,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
DiagnosticFormat, Severity,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -63,4 +63,118 @@ mod tests {
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit c9b99e4.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[no-indented-block]: Expected an indented block
|
||||
/// --> example.py:3:1
|
||||
/// |
|
||||
/// 2 | if False:
|
||||
/// | ^
|
||||
/// 3 | print()
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the end of the previous line instead of the start of the next.
|
||||
#[test]
|
||||
fn empty_span_after_line_terminator() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add(
|
||||
"example.py",
|
||||
r#"
|
||||
if False:
|
||||
print()
|
||||
"#,
|
||||
);
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"no-indented-block",
|
||||
Severity::Error,
|
||||
"Expected an indented block",
|
||||
)
|
||||
.primary("example.py", "3:0", "3:0", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[no-indented-block]: Expected an indented block
|
||||
--> example.py:3:1
|
||||
|
|
||||
2 | if False:
|
||||
3 | print()
|
||||
| ^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit 2922490.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
/// --> example.py:1:25
|
||||
/// |
|
||||
/// 1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
/// | ^
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the `f` in the f-string instead of the start of the invalid
|
||||
/// character (`^Z`).
|
||||
#[test]
|
||||
fn unprintable_characters() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "nested_fstrings = f'{f'{f''}'}'");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:24", "1:24", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:25
|
||||
|
|
||||
1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_unprintable_characters() -> std::io::Result<()> {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:1", "1:1", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:2
|
||||
|
|
||||
1 | ␈␛
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ ty_python_semantic = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
memchr = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::StringImports;
|
||||
use ruff_python_ast::visitor::source_order::{
|
||||
SourceOrderVisitor, walk_expr, walk_module, walk_stmt,
|
||||
};
|
||||
@@ -10,13 +11,13 @@ pub(crate) struct Collector<'a> {
|
||||
/// The path to the current module.
|
||||
module_path: Option<&'a [String]>,
|
||||
/// Whether to detect imports from string literals.
|
||||
string_imports: bool,
|
||||
string_imports: StringImports,
|
||||
/// The collected imports from the Python AST.
|
||||
imports: Vec<CollectedImport>,
|
||||
}
|
||||
|
||||
impl<'a> Collector<'a> {
|
||||
pub(crate) fn new(module_path: Option<&'a [String]>, string_imports: bool) -> Self {
|
||||
pub(crate) fn new(module_path: Option<&'a [String]>, string_imports: StringImports) -> Self {
|
||||
Self {
|
||||
module_path,
|
||||
string_imports,
|
||||
@@ -118,7 +119,7 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
|
||||
| Stmt::Continue(_)
|
||||
| Stmt::IpyEscapeCommand(_) => {
|
||||
// Only traverse simple statements when string imports is enabled.
|
||||
if self.string_imports {
|
||||
if self.string_imports.enabled {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
}
|
||||
@@ -126,20 +127,26 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'ast Expr) {
|
||||
if self.string_imports {
|
||||
if self.string_imports.enabled {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) = expr
|
||||
{
|
||||
// Determine whether the string literal "looks like" an import statement: contains
|
||||
// a dot, and consists solely of valid Python identifiers.
|
||||
let value = value.to_str();
|
||||
if let Some(module_name) = ModuleName::new(value) {
|
||||
self.imports.push(CollectedImport::Import(module_name));
|
||||
// Determine whether the string literal "looks like" an import statement: contains
|
||||
// the requisite number of dots, and consists solely of valid Python identifiers.
|
||||
if self.string_imports.min_dots == 0
|
||||
|| memchr::memchr_iter(b'.', value.as_bytes()).count()
|
||||
>= self.string_imports.min_dots
|
||||
{
|
||||
if let Some(module_name) = ModuleName::new(value) {
|
||||
self.imports.push(CollectedImport::Import(module_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use ruff_python_parser::{Mode, ParseOptions, parse};
|
||||
use crate::collector::Collector;
|
||||
pub use crate::db::ModuleDb;
|
||||
use crate::resolver::Resolver;
|
||||
pub use crate::settings::{AnalyzeSettings, Direction};
|
||||
pub use crate::settings::{AnalyzeSettings, Direction, StringImports};
|
||||
|
||||
mod collector;
|
||||
mod db;
|
||||
@@ -26,7 +26,7 @@ impl ModuleImports {
|
||||
db: &ModuleDb,
|
||||
path: &SystemPath,
|
||||
package: Option<&SystemPath>,
|
||||
string_imports: bool,
|
||||
string_imports: StringImports,
|
||||
) -> Result<Self> {
|
||||
// Read and parse the source code.
|
||||
let source = std::fs::read_to_string(path)?;
|
||||
@@ -42,13 +42,11 @@ impl ModuleImports {
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
let Some(resolved) = Resolver::new(db).resolve(import) else {
|
||||
continue;
|
||||
};
|
||||
let Some(path) = resolved.as_system_path() else {
|
||||
continue;
|
||||
};
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
for resolved in Resolver::new(db).resolve(import) {
|
||||
if let Some(path) = resolved.as_system_path() {
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resolved_imports)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_db::files::FilePath;
|
||||
use ty_python_semantic::resolve_module;
|
||||
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
|
||||
|
||||
use crate::ModuleDb;
|
||||
use crate::collector::CollectedImport;
|
||||
@@ -16,24 +16,67 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
|
||||
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
// Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
|
||||
let file = self.resolve_module(&import);
|
||||
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
if let Some(file) = self.resolve_module(&import) {
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = (file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
return std::iter::once(Some(file))
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten();
|
||||
}
|
||||
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
let parent = import.parent();
|
||||
let file = parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_module(parent));
|
||||
|
||||
let module = resolve_module(self.db, &import).or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| {
|
||||
parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_real_module(parent))
|
||||
})
|
||||
.flatten();
|
||||
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_real_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub struct AnalyzeSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub preview: PreviewMode,
|
||||
pub target_version: PythonVersion,
|
||||
pub detect_string_imports: bool,
|
||||
pub string_imports: StringImports,
|
||||
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
|
||||
pub extension: ExtensionMapping,
|
||||
}
|
||||
@@ -26,7 +26,7 @@ impl fmt::Display for AnalyzeSettings {
|
||||
self.exclude,
|
||||
self.preview,
|
||||
self.target_version,
|
||||
self.detect_string_imports,
|
||||
self.string_imports,
|
||||
self.extension | debug,
|
||||
self.include_dependencies | debug,
|
||||
]
|
||||
@@ -35,6 +35,31 @@ impl fmt::Display for AnalyzeSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, CacheKey)]
|
||||
pub struct StringImports {
|
||||
pub enabled: bool,
|
||||
pub min_dots: usize,
|
||||
}
|
||||
|
||||
impl Default for StringImports {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
min_dots: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StringImports {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.enabled {
|
||||
write!(f, "enabled (min_dots: {})", self.min_dots)
|
||||
} else {
|
||||
write!(f, "disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, CacheKey)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
|
||||
@@ -25,5 +25,5 @@ def my_func():
|
||||
|
||||
# t-strings - all ok
|
||||
t"0.0.0.0"
|
||||
"0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
|
||||
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
|
||||
t"0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
|
||||
t"0.0.0.0" t"0.0.0.0{expr}0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
|
||||
|
||||
@@ -94,7 +94,7 @@ except Exception:
|
||||
logging.error("...", exc_info=True)
|
||||
|
||||
|
||||
from logging import error, exception
|
||||
from logging import critical, error, exception
|
||||
|
||||
try:
|
||||
pass
|
||||
@@ -114,6 +114,23 @@ except Exception:
|
||||
error("...", exc_info=None)
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
critical("...")
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
critical("...", exc_info=False)
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
critical("...", exc_info=None)
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
@@ -125,6 +142,13 @@ try:
|
||||
except Exception:
|
||||
error("...", exc_info=True)
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
critical("...", exc_info=True)
|
||||
|
||||
|
||||
try:
|
||||
...
|
||||
except Exception as e:
|
||||
|
||||
@@ -650,3 +650,17 @@ f"""This is a test. {
|
||||
if True else
|
||||
"Don't add a trailing comma here ->"
|
||||
}"""
|
||||
|
||||
type X[
|
||||
T
|
||||
] = T
|
||||
def f[
|
||||
T
|
||||
](): pass
|
||||
class C[
|
||||
T
|
||||
]: pass
|
||||
|
||||
type X[T,] = T
|
||||
def f[T,](): pass
|
||||
class C[T,]: pass
|
||||
@@ -289,4 +289,19 @@ def f():
|
||||
i = "xyz"
|
||||
result = []
|
||||
for i in range(3):
|
||||
result.append((x for x in [i]))
|
||||
result.append((x for x in [i]))
|
||||
|
||||
G_INDEX = None
|
||||
def f():
|
||||
global G_INDEX
|
||||
result = []
|
||||
for G_INDEX in range(3):
|
||||
result.append(G_INDEX)
|
||||
|
||||
def f():
|
||||
NL_INDEX = None
|
||||
def x():
|
||||
nonlocal NL_INDEX
|
||||
result = []
|
||||
for NL_INDEX in range(3):
|
||||
result.append(NL_INDEX)
|
||||
@@ -143,3 +143,23 @@ class NotAMethodButHardToDetect:
|
||||
# without risking false positives elsewhere or introducing complex heuristics
|
||||
# that users would find surprising and confusing
|
||||
FOO = sorted([x for x in BAR], key=lambda x: x.baz)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19305
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def my_fixture_with_param(request):
|
||||
return request.param
|
||||
|
||||
@pytest.fixture()
|
||||
def my_fixture_with_param2(request):
|
||||
return request.param
|
||||
|
||||
|
||||
# Decorated function (should be ignored)
|
||||
def custom_decorator(func):
|
||||
return func
|
||||
|
||||
@custom_decorator
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
@@ -55,3 +55,12 @@ _ = Decimal(0.1)
|
||||
_ = Decimal(-0.5)
|
||||
_ = Decimal(5.0)
|
||||
_ = decimal.Decimal(4.2)
|
||||
|
||||
# Cases with int and bool - should produce safe fixes
|
||||
_ = Decimal.from_float(1)
|
||||
_ = Decimal.from_float(True)
|
||||
|
||||
# Cases with non-finite floats - should produce safe fixes
|
||||
_ = Decimal.from_float(float("-nan"))
|
||||
_ = Decimal.from_float(float("\x2dnan"))
|
||||
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
@@ -65,3 +65,62 @@ class Foo:
|
||||
bar = "should've used attrs"
|
||||
|
||||
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18950
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(
|
||||
self,
|
||||
bar: int = (x := 1) # comment
|
||||
,
|
||||
baz: int = (y := 2), # comment
|
||||
foo = (a := 1) # comment
|
||||
,
|
||||
faz = (b := 2), # comment
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(
|
||||
self,
|
||||
bar: int = 1, # comment
|
||||
baz: int = 2, # comment
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(
|
||||
self,
|
||||
arg1: int = (1) # comment
|
||||
,
|
||||
arg2: int = ((1)) # comment
|
||||
,
|
||||
arg2: int = (i for i in range(10)) # comment
|
||||
,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
# makes little sense, but is valid syntax
|
||||
def fun_with_python_syntax():
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(
|
||||
self,
|
||||
bar: (int) = (yield from range(5)) # comment
|
||||
,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
return Foo
|
||||
|
||||
@@ -53,3 +53,16 @@ regex.subn(br"""eak your machine with rm -""", rf"""/""")
|
||||
regex.splititer(both, non_literal)
|
||||
regex.subf(f, lambda _: r'means', '"format"')
|
||||
regex.subfn(fn, f'''a$1n't''', lambda: "'function'")
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/16713
|
||||
re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
re.compile("\b") # without fix
|
||||
re.compile("\"") # without fix
|
||||
re.compile("\'") # without fix
|
||||
re.compile('\"') # without fix
|
||||
re.compile('\'') # without fix
|
||||
re.compile("\\") # without fix
|
||||
re.compile("\101") # without fix
|
||||
re.compile("a\
|
||||
b") # without fix
|
||||
|
||||
@@ -91,3 +91,20 @@ regex.subf(
|
||||
br''br""br''
|
||||
)
|
||||
regex.subfn(br'I\s\nee*d\s[O0o]me\x20\Qoffe\E, ' br'b')
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/16713
|
||||
re.compile(
|
||||
"["
|
||||
"\U0001F600-\U0001F64F" # emoticons
|
||||
"\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
"\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
"\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
"\U00002702-\U000027B0"
|
||||
"\U000024C2-\U0001F251"
|
||||
"\u200d" # zero width joiner
|
||||
"\u200c" # zero width non-joiner
|
||||
"\\u200c" # must not be escaped in a raw string
|
||||
"]+",
|
||||
flags=re.UNICODE,
|
||||
)
|
||||
|
||||
3
crates/ruff_linter/resources/test/fixtures/ruff/RUF039_py_version_sensitive.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/ruff/RUF039_py_version_sensitive.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import re
|
||||
|
||||
re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
|
||||
@@ -3216,6 +3216,11 @@ impl<'a> LintContext<'a> {
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &Diagnostic> {
|
||||
self.diagnostics.get_mut().iter()
|
||||
}
|
||||
|
||||
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
|
||||
pub(crate) const fn settings(&self) -> &LinterSettings {
|
||||
self.settings
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction for mutating a diagnostic.
|
||||
|
||||
@@ -16,7 +16,6 @@ use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
@@ -27,7 +26,6 @@ pub(crate) fn check_tokens(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
settings: &LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
context: &mut LintContext,
|
||||
@@ -42,15 +40,8 @@ pub(crate) fn check_tokens(
|
||||
Rule::BlankLinesAfterFunctionOrClass,
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
BlankLinesChecker::new(
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
)
|
||||
.check_lines(tokens);
|
||||
BlankLinesChecker::new(locator, stylist, source_type, cell_offsets, context)
|
||||
.check_lines(tokens);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
|
||||
@@ -63,12 +54,12 @@ pub(crate) fn check_tokens(
|
||||
|
||||
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
|
||||
for range in comment_ranges {
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range);
|
||||
}
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::CommentedOutCode) {
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
|
||||
@@ -110,7 +101,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MultiLineImplicitStringConcatenation,
|
||||
]) {
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if context.any_rule_enabled(&[
|
||||
|
||||
@@ -188,7 +188,6 @@ pub fn check_path(
|
||||
locator,
|
||||
indexer,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
|
||||
&mut context,
|
||||
@@ -473,7 +472,7 @@ pub fn lint_only(
|
||||
&& !is_py314_support_enabled(settings)
|
||||
{
|
||||
warn_user_once!(
|
||||
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
|
||||
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -584,7 +583,7 @@ pub fn lint_fix<'a>(
|
||||
&& !is_py314_support_enabled(settings)
|
||||
{
|
||||
warn_user_once!(
|
||||
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
|
||||
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,86 +118,6 @@ impl<'a> Locator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("❤️🧡💛💚💙💜");
|
||||
/// assert_eq!(locator.text_len(), TextSize::from(26));
|
||||
/// assert!(!locator.contents().is_char_boundary(13));
|
||||
///
|
||||
/// let closest = locator.ceil_char_boundary(TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&locator.contents()[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("Hello");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let locator = Locator::new("α");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(&self, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(self.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| self.contents.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
/// Take the source code between the given [`TextRange`].
|
||||
#[inline]
|
||||
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {
|
||||
|
||||
@@ -6,12 +6,13 @@ use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
|
||||
};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
@@ -76,6 +77,12 @@ impl TextEmitter {
|
||||
self.config = self.config.preview(preview);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_color(mut self, color: bool) -> Self {
|
||||
self.config = self.config.color(color);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for TextEmitter {
|
||||
@@ -370,9 +377,8 @@ impl<'a> SourceCode<'a> {
|
||||
if self.text.as_bytes()[self.annotation_range.start().to_usize() - 1] != b'\n' {
|
||||
return self;
|
||||
}
|
||||
let locator = Locator::new(&self.text);
|
||||
let start = self.annotation_range.start();
|
||||
let end = locator.ceil_char_boundary(start + TextSize::from(1));
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
SourceCode {
|
||||
annotation_range: TextRange::new(start, end),
|
||||
..self
|
||||
|
||||
@@ -225,3 +225,8 @@ pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSet
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19390
|
||||
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use ruff_text_size::TextRange;
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
use crate::rules::eradicate::detection::comment_contains_code;
|
||||
@@ -51,7 +50,6 @@ pub(crate) fn commented_out_code(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let mut comments = comment_ranges.into_iter().peekable();
|
||||
// Iterate over all comments in the document.
|
||||
@@ -65,7 +63,9 @@ pub(crate) fn commented_out_code(
|
||||
}
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
if is_own_line_comment(line)
|
||||
&& comment_contains_code(line, &context.settings().task_tags[..])
|
||||
{
|
||||
if let Some(mut diagnostic) =
|
||||
context.report_diagnostic_if_enabled(CommentedOutCode, range)
|
||||
{
|
||||
|
||||
@@ -47,9 +47,10 @@ use crate::checkers::ast::Checker;
|
||||
/// raise
|
||||
/// ```
|
||||
///
|
||||
/// Exceptions that are logged via `logging.exception()` or `logging.error()`
|
||||
/// with `exc_info` enabled will _not_ be flagged, as this is a common pattern
|
||||
/// for propagating exception traces:
|
||||
/// Exceptions that are logged via `logging.exception()` or are logged via
|
||||
/// `logging.error()` or `logging.critical()` with `exc_info` enabled will
|
||||
/// _not_ be flagged, as this is a common pattern for propagating exception
|
||||
/// traces:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// foo()
|
||||
@@ -201,7 +202,7 @@ impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
|
||||
) {
|
||||
if match attr.as_str() {
|
||||
"exception" => true,
|
||||
"error" => arguments
|
||||
"error" | "critical" => arguments
|
||||
.find_keyword("exc_info")
|
||||
.is_some_and(|keyword| is_const_true(&keyword.value)),
|
||||
_ => false,
|
||||
@@ -214,7 +215,7 @@ impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
|
||||
if self.semantic.resolve_qualified_name(func).is_some_and(
|
||||
|qualified_name| match qualified_name.segments() {
|
||||
["logging", "exception"] => true,
|
||||
["logging", "error"] => arguments
|
||||
["logging", "error" | "critical"] => arguments
|
||||
.find_keyword("exc_info")
|
||||
.is_some_and(|keyword| is_const_true(&keyword.value)),
|
||||
_ => false,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_blind_except/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
BLE.py:25:8: BLE001 Do not catch blind exception: `BaseException`
|
||||
|
|
||||
@@ -121,3 +120,30 @@ BLE.py:113:8: BLE001 Do not catch blind exception: `Exception`
|
||||
| ^^^^^^^^^ BLE001
|
||||
114 | error("...", exc_info=None)
|
||||
|
|
||||
|
||||
BLE.py:119:8: BLE001 Do not catch blind exception: `Exception`
|
||||
|
|
||||
117 | try:
|
||||
118 | pass
|
||||
119 | except Exception:
|
||||
| ^^^^^^^^^ BLE001
|
||||
120 | critical("...")
|
||||
|
|
||||
|
||||
BLE.py:125:8: BLE001 Do not catch blind exception: `Exception`
|
||||
|
|
||||
123 | try:
|
||||
124 | pass
|
||||
125 | except Exception:
|
||||
| ^^^^^^^^^ BLE001
|
||||
126 | critical("...", exc_info=False)
|
||||
|
|
||||
|
||||
BLE.py:131:8: BLE001 Do not catch blind exception: `Exception`
|
||||
|
|
||||
129 | try:
|
||||
130 | pass
|
||||
131 | except Exception:
|
||||
| ^^^^^^^^^ BLE001
|
||||
132 | critical("...", exc_info=None)
|
||||
|
|
||||
|
||||
@@ -27,4 +27,23 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview__{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_commas").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::preview::is_trailing_comma_type_params_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Violation};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
@@ -24,6 +26,8 @@ enum TokenType {
|
||||
Def,
|
||||
For,
|
||||
Lambda,
|
||||
Class,
|
||||
Type,
|
||||
Irrelevant,
|
||||
}
|
||||
|
||||
@@ -69,6 +73,8 @@ impl From<(TokenKind, TextRange)> for SimpleToken {
|
||||
TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
TokenKind::Rbrace => TokenType::ClosingBracket,
|
||||
TokenKind::Def => TokenType::Def,
|
||||
TokenKind::Class => TokenType::Class,
|
||||
TokenKind::Type => TokenType::Type,
|
||||
TokenKind::For => TokenType::For,
|
||||
TokenKind::Lambda => TokenType::Lambda,
|
||||
// Import treated like a function.
|
||||
@@ -98,6 +104,8 @@ enum ContextType {
|
||||
Dict,
|
||||
/// Lambda parameter list, e.g. `lambda a, b`.
|
||||
LambdaParameters,
|
||||
/// Type parameter list, e.g. `def foo[T, U](): ...`
|
||||
TypeParameters,
|
||||
}
|
||||
|
||||
/// Comma context - described a comma-delimited "situation".
|
||||
@@ -290,7 +298,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
|
||||
|
||||
check_token(token, prev, prev_prev, context, locator, lint_context);
|
||||
|
||||
@@ -326,6 +334,7 @@ fn check_token(
|
||||
ContextType::No => false,
|
||||
ContextType::FunctionParameters => true,
|
||||
ContextType::CallArguments => true,
|
||||
ContextType::TypeParameters => true,
|
||||
// `(1)` is not equivalent to `(1,)`.
|
||||
ContextType::Tuple => context.num_commas != 0,
|
||||
// `x[1]` is not equivalent to `x[1,]`.
|
||||
@@ -408,6 +417,7 @@ fn update_context(
|
||||
prev: SimpleToken,
|
||||
prev_prev: SimpleToken,
|
||||
stack: &mut Vec<Context>,
|
||||
settings: &LinterSettings,
|
||||
) -> Context {
|
||||
let new_context = match token.ty {
|
||||
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
|
||||
@@ -417,6 +427,17 @@ fn update_context(
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
|
||||
match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
|
||||
Context::new(ContextType::TypeParameters)
|
||||
}
|
||||
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
}
|
||||
}
|
||||
TokenType::OpeningSquareBracket => match prev.ty {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
Context::new(ContextType::Subscript)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
3 | *args
|
||||
| ^^^^^
|
||||
4 | )
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^
|
||||
7 | pass
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:38: COM819 Trailing comma prohibited
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^ COM819
|
||||
7 | pass
|
||||
|
|
||||
= help: Remove trailing comma
|
||||
@@ -11,7 +11,6 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
@@ -108,13 +107,15 @@ pub(crate) fn implicit(
|
||||
tokens: &Tokens,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
for (a_token, b_token) in tokens
|
||||
.iter()
|
||||
.filter(|token| {
|
||||
token.kind() != TokenKind::Comment
|
||||
&& (settings.flake8_implicit_str_concat.allow_multiline
|
||||
&& (context
|
||||
.settings()
|
||||
.flake8_implicit_str_concat
|
||||
.allow_multiline
|
||||
|| token.kind() != TokenKind::NonLogicalNewline)
|
||||
})
|
||||
.tuple_windows()
|
||||
|
||||
@@ -249,6 +249,11 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
|
||||
.iter()
|
||||
.find(|binding| for_stmt.target.range() == binding.range)
|
||||
.unwrap();
|
||||
// If the target variable is global (e.g., `global INDEX`) or nonlocal (e.g., `nonlocal INDEX`),
|
||||
// then it is intended to be used elsewhere outside the for loop.
|
||||
if target_binding.is_global() || target_binding.is_nonlocal() {
|
||||
return;
|
||||
}
|
||||
// If any references to the loop target variable are after the loop,
|
||||
// then converting it into a comprehension would cause a NameError
|
||||
if target_binding
|
||||
|
||||
@@ -263,5 +263,7 @@ PERF401.py:292:9: PERF401 Use a list comprehension to create a transformed list
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -612,6 +612,8 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -623,3 +625,6 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 |- for i in range(3):
|
||||
292 |- result.append((x for x in [i]))
|
||||
290 |+ result = [(x for x in [i]) for i in range(3)]
|
||||
293 291 |
|
||||
294 292 | G_INDEX = None
|
||||
295 293 | def f():
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::checkers::ast::{DiagnosticGuard, LintContext};
|
||||
use crate::checkers::logical_lines::expand_indent;
|
||||
use crate::line_width::IndentWidth;
|
||||
use crate::rules::pycodestyle::helpers::is_non_logical_token;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, Locator, Violation};
|
||||
|
||||
/// Number of blank lines around top level classes and functions.
|
||||
@@ -694,14 +693,12 @@ pub(crate) struct BlankLinesChecker<'a, 'b> {
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
settings: &'a LinterSettings,
|
||||
}
|
||||
|
||||
impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
pub(crate) fn new(
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
settings: &'a LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
@@ -712,7 +709,6 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +729,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
let line_preprocessor = LinePreprocessor::new(
|
||||
tokens,
|
||||
self.locator,
|
||||
self.settings.tab_size,
|
||||
self.context.settings().tab_size,
|
||||
self.cell_offsets,
|
||||
);
|
||||
|
||||
@@ -879,7 +875,8 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
// `isort` defaults to 2 if before a class or function definition (except in stubs where it is one) and 1 otherwise.
|
||||
// Defaulting to 2 (or 1 in stubs) here is correct because the variable is only used when testing the
|
||||
// blank lines before a class or function definition.
|
||||
u32::try_from(self.settings.isort.lines_after_imports).unwrap_or(max_lines_level)
|
||||
u32::try_from(self.context.settings().isort.lines_after_imports)
|
||||
.unwrap_or(max_lines_level)
|
||||
} else {
|
||||
max_lines_level
|
||||
}
|
||||
@@ -941,8 +938,10 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
(LogicalLineKind::Import, Follows::FromImport)
|
||||
| (LogicalLineKind::FromImport, Follows::Import)
|
||||
) {
|
||||
max_lines_level
|
||||
.max(u32::try_from(self.settings.isort.lines_between_types).unwrap_or(u32::MAX))
|
||||
max_lines_level.max(
|
||||
u32::try_from(self.context.settings().isort.lines_between_types)
|
||||
.unwrap_or(u32::MAX),
|
||||
)
|
||||
} else {
|
||||
expected_blank_lines_before_definition
|
||||
};
|
||||
|
||||
@@ -6,8 +6,9 @@ use ruff_python_ast::{
|
||||
use ruff_python_semantic::{SemanticModel, analyze::typing};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
|
||||
@@ -35,10 +36,14 @@ use crate::checkers::ast::Checker;
|
||||
/// url = "www.example.com"
|
||||
/// suffix = url.rsplit(".", maxsplit=1)[-1]
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `**kwargs`, as
|
||||
/// adding a `maxsplit` keyword to such a call may lead to a duplicate keyword argument error.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingMaxsplitArg {
|
||||
index: SliceBoundary,
|
||||
actual_split_type: String,
|
||||
suggested_split_type: String,
|
||||
}
|
||||
|
||||
/// Represents the index of the slice used for this rule (which can only be 0 or -1)
|
||||
@@ -47,25 +52,27 @@ enum SliceBoundary {
|
||||
Last,
|
||||
}
|
||||
|
||||
impl Violation for MissingMaxsplitArg {
|
||||
impl AlwaysFixableViolation for MissingMaxsplitArg {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
index,
|
||||
actual_split_type,
|
||||
actual_split_type: _,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
let suggested_split_type = match index {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
format!("Replace with `{suggested_split_type}(..., maxsplit=1)`.")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
actual_split_type,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
if actual_split_type == suggested_split_type {
|
||||
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
|
||||
} else {
|
||||
format!(
|
||||
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
|
||||
)
|
||||
format!("Use `str.{suggested_split_type}()` and pass `maxsplit=1`")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +130,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
};
|
||||
|
||||
// Check the function is "split" or "rsplit"
|
||||
let attr = attr.as_str();
|
||||
if !matches!(attr, "split" | "rsplit") {
|
||||
let actual_split_type = attr.as_str();
|
||||
if !matches!(actual_split_type, "split" | "rsplit") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,11 +168,48 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
let suggested_split_type = match slice_boundary {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
let maxsplit_argument_edit = fix::edits::add_argument(
|
||||
"maxsplit=1",
|
||||
arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
|
||||
// Only change `actual_split_type` if it doesn't match `suggested_split_type`
|
||||
let split_type_edit: Option<Edit> = if actual_split_type == suggested_split_type {
|
||||
None
|
||||
} else {
|
||||
Some(Edit::range_replacement(
|
||||
suggested_split_type.to_string(),
|
||||
attr.range(),
|
||||
))
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
MissingMaxsplitArg {
|
||||
index: slice_boundary,
|
||||
actual_split_type: attr.to_string(),
|
||||
actual_split_type: actual_split_type.to_string(),
|
||||
suggested_split_type: suggested_split_type.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edits(
|
||||
maxsplit_argument_edit,
|
||||
split_type_edit,
|
||||
// If keyword.arg is `None` (i.e. if the function call contains `**kwargs`), mark the fix as unsafe
|
||||
if arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
12 | # Errors
|
||||
13 | ## Test split called directly on string literal
|
||||
@@ -10,8 +10,19 @@ missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 |-"1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
14 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
13 | ## Test split called directly on string literal
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -20,8 +31,19 @@ missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 |-"1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
15 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -29,8 +51,19 @@ missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 |-"1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
16 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -39,8 +72,19 @@ missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
18 |
|
||||
19 | ## Test split called on string variable
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 |-"1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
17 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -48,8 +92,19 @@ missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 |-SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
20 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -58,8 +113,19 @@ missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 |-SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
21 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -67,8 +133,19 @@ missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 |-SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
22 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -77,8 +154,19 @@ missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
24 |
|
||||
25 | ## Test split called on class attribute
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 |-SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
23 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -86,8 +174,19 @@ missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 |-Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
26 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -96,8 +195,19 @@ missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 |-Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
27 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -105,8 +215,19 @@ missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 |-Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
28 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -115,8 +236,19 @@ missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
30 |
|
||||
31 | ## Test split called on sliced string
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 |-Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
29 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -124,8 +256,19 @@ missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 |-"1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
32 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -134,8 +277,19 @@ missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |-"1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |+"1,2,3"[::-1][::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -144,8 +298,19 @@ missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |-SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -154,8 +319,19 @@ missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 |-Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
35 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -164,8 +340,19 @@ missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 |-"1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
36 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -173,8 +360,19 @@ missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |-SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -183,8 +381,19 @@ missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
39 |
|
||||
40 | ## Test sep given as named argument
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 |-Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
38 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -192,8 +401,19 @@ missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 |-"1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
41 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -202,8 +422,19 @@ missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 |-"1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
42 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -211,8 +442,19 @@ missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 |-"1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
43 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -221,8 +463,19 @@ missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
45 |
|
||||
46 | ## Special cases
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 |-"1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
44 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -230,8 +483,19 @@ missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 |-"1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
47 |+"1,2,3".split("\n", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -239,8 +503,19 @@ missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 |-"1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
48 |+"1,2,3".rsplit("split", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
@@ -249,8 +524,19 @@ missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
50 |
|
||||
51 | ## Test class attribute named split
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 |-"1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
49 |+"1,2,3".split("rsplit", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -258,8 +544,19 @@ missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 |-Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
52 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -268,8 +565,19 @@ missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 |-Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
53 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -277,8 +585,19 @@ missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 |-Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
54 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -287,15 +606,37 @@ missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
56 |
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 |-Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
55 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 |-"1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
58 |+"1,2,3".split(maxsplit=1, **{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
59 59 |
|
||||
60 60 |
|
||||
61 61 | # OK
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
177 | # Errors
|
||||
178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
@@ -304,8 +645,19 @@ missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
176 176 | ## TODO: These require the ability to resolve a dict variable name to a value
|
||||
177 177 | # Errors
|
||||
178 178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
179 |-"1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
179 |+"1,2,3".split(maxsplit=1, **kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
@@ -314,11 +666,29 @@ missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
179 179 | "1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 |-"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
182 |+"1,2,3".split(",", maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 |-"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
184 |+"1,2,3".split(maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
@@ -104,6 +104,13 @@ pub(crate) fn reimplemented_operator(checker: &Checker, target: &FunctionLike) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip decorated functions
|
||||
if let FunctionLike::Function(func) = target {
|
||||
if !func.decorator_list.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(params) = target.parameters() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -180,18 +180,18 @@ fn is_valid_argument_type(
|
||||
typing::is_float(binding, semantic),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.unwrap_or((false, false))
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
|
||||
match (method_name, constructor) {
|
||||
// Decimal.from_float accepts int, bool, float
|
||||
// Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap)
|
||||
(MethodName::FromFloat, Constructor::Decimal) => match resolved_type {
|
||||
ResolvedPythonType::Atom(PythonType::Number(
|
||||
NumberLike::Integer | NumberLike::Bool | NumberLike::Float,
|
||||
NumberLike::Integer | NumberLike::Bool,
|
||||
)) => true,
|
||||
ResolvedPythonType::Unknown => is_int || is_float,
|
||||
ResolvedPythonType::Unknown => is_int,
|
||||
_ => false,
|
||||
},
|
||||
// Fraction.from_float accepts int, bool, float
|
||||
@@ -286,10 +286,8 @@ fn handle_non_finite_float_special_case(
|
||||
let [float_arg] = arguments.args.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
as_non_finite_float_string_literal(float_arg)?;
|
||||
|
||||
let replacement_arg = checker.locator().slice(float_arg).to_string();
|
||||
let replacement_text = format!("{constructor_name}({replacement_arg})");
|
||||
let normalized = as_non_finite_float_string_literal(float_arg)?;
|
||||
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
|
||||
Some(Edit::range_replacement(replacement_text, call.range()))
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
10 10 | _ = fractions.Fraction.from_float(4.2)
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
@@ -179,7 +179,7 @@ FURB164.py:14:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
@@ -200,7 +200,7 @@ FURB164.py:15:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
@@ -221,7 +221,7 @@ FURB164.py:16:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
@@ -242,7 +242,7 @@ FURB164.py:17:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
16 16 | _ = Decimal.from_float(5.0)
|
||||
@@ -310,7 +310,7 @@ FURB164.py:20:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
18 18 | _ = Decimal.from_float(float("inf"))
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 |-_ = Decimal.from_float(float("Infinity"))
|
||||
20 |+_ = Decimal("Infinity")
|
||||
20 |+_ = Decimal("infinity")
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
@@ -331,7 +331,7 @@ FURB164.py:21:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 20 | _ = Decimal.from_float(float("Infinity"))
|
||||
21 |-_ = Decimal.from_float(float("-Infinity"))
|
||||
21 |+_ = Decimal("-Infinity")
|
||||
21 |+_ = Decimal("-infinity")
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
@@ -373,7 +373,7 @@ FURB164.py:23:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 |-_ = Decimal.from_float(float("-NaN "))
|
||||
23 |+_ = Decimal("-NaN ")
|
||||
23 |+_ = Decimal("-nan")
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
@@ -394,7 +394,7 @@ FURB164.py:24:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 |-_ = Decimal.from_float(float(" \n+nan \t"))
|
||||
24 |+_ = Decimal(" \n+nan \t")
|
||||
24 |+_ = Decimal("+nan")
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
@@ -415,7 +415,7 @@ FURB164.py:25:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 |-_ = Decimal.from_float(float(" iNf \n\t "))
|
||||
25 |+_ = Decimal(" iNf \n\t ")
|
||||
25 |+_ = Decimal("inf")
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
@@ -436,7 +436,7 @@ FURB164.py:26:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 |-_ = Decimal.from_float(float(" -inF\n \t"))
|
||||
26 |+_ = Decimal(" -inF\n \t")
|
||||
26 |+_ = Decimal("-inf")
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
@@ -456,7 +456,7 @@ FURB164.py:27:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 |-_ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
27 |+_ = Decimal(" InfinIty \n\t ")
|
||||
27 |+_ = Decimal("infinity")
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
@@ -477,7 +477,7 @@ FURB164.py:28:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 |-_ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
28 |+_ = Decimal(" -InfinIty\n \t")
|
||||
28 |+_ = Decimal("-infinity")
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2"))
|
||||
@@ -612,3 +612,96 @@ FURB164.py:45:5: FURB164 [*] Verbose method `from_float` in `Fraction` construct
|
||||
46 46 |
|
||||
47 47 | # OK - should not trigger the rule
|
||||
48 48 | _ = Fraction(0.1)
|
||||
|
||||
FURB164.py:60:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
61 | _ = Decimal.from_float(True)
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | _ = decimal.Decimal(4.2)
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 |-_ = Decimal.from_float(1)
|
||||
60 |+_ = Decimal(1)
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
||||
FURB164.py:61:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
61 | _ = Decimal.from_float(True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
62 |
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 60 | _ = Decimal.from_float(1)
|
||||
61 |-_ = Decimal.from_float(True)
|
||||
61 |+_ = Decimal(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
|
||||
FURB164.py:64:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 |-_ = Decimal.from_float(float("-nan"))
|
||||
64 |+_ = Decimal("-nan")
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:65:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 |-_ = Decimal.from_float(float("\x2dnan"))
|
||||
65 |+_ = Decimal("-nan")
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:66:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 |-_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
66 |+_ = Decimal("-nan")
|
||||
|
||||
@@ -555,6 +555,44 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnrawRePattern, Path::new("RUF039_py_version_sensitive.py"))]
|
||||
fn preview_rules_py37(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__py37__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
unresolved_target_version: PythonVersion::PY37.into(),
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnrawRePattern, Path::new("RUF039_py_version_sensitive.py"))]
|
||||
fn preview_rules_py38(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__py38__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
unresolved_target_version: PythonVersion::PY38.into(),
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
|
||||
fn custom_regexp_preset(
|
||||
|
||||
@@ -178,11 +178,10 @@ pub(crate) fn ambiguous_unicode_character_comment(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
range: TextRange,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let text = locator.slice(range);
|
||||
for candidate in ambiguous_unicode_character(text, range, settings) {
|
||||
candidate.into_diagnostic(Context::Comment, settings, context);
|
||||
for candidate in ambiguous_unicode_character(text, range, context.settings()) {
|
||||
candidate.into_diagnostic(Context::Comment, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,13 +341,12 @@ impl Candidate {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_diagnostic(
|
||||
self,
|
||||
context: Context,
|
||||
settings: &LinterSettings,
|
||||
lint_context: &LintContext,
|
||||
) {
|
||||
if !settings.allowed_confusables.contains(&self.confusable) {
|
||||
fn into_diagnostic(self, context: Context, lint_context: &LintContext) {
|
||||
if !lint_context
|
||||
.settings()
|
||||
.allowed_confusables
|
||||
.contains(&self.confusable)
|
||||
{
|
||||
let char_range = TextRange::at(self.offset, self.confusable.text_len());
|
||||
match context {
|
||||
Context::String => lint_context.report_diagnostic_if_enabled(
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_semantic::{Scope, ScopeKind};
|
||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||
use ruff_source_file::LineRanges;
|
||||
@@ -117,13 +118,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||
|
||||
if !stopped_fixes {
|
||||
diagnostic.try_set_fix(|| {
|
||||
use_initvar(
|
||||
current_scope,
|
||||
function_def,
|
||||
¶meter.parameter,
|
||||
default,
|
||||
checker,
|
||||
)
|
||||
use_initvar(current_scope, function_def, parameter, default, checker)
|
||||
});
|
||||
// Need to stop fixes as soon as there is a parameter we cannot fix.
|
||||
// Otherwise, we risk a syntax error (a parameter without a default
|
||||
@@ -138,10 +133,11 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||
fn use_initvar(
|
||||
current_scope: &Scope,
|
||||
post_init_def: &ast::StmtFunctionDef,
|
||||
parameter: &ast::Parameter,
|
||||
parameter_with_default: &ast::ParameterWithDefault,
|
||||
default: &ast::Expr,
|
||||
checker: &Checker,
|
||||
) -> anyhow::Result<Fix> {
|
||||
let parameter = ¶meter_with_default.parameter;
|
||||
if current_scope.has(¶meter.name) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Cannot add a `{}: InitVar` field to the class body, as a field by that name already exists",
|
||||
@@ -157,17 +153,25 @@ fn use_initvar(
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let locator = checker.locator();
|
||||
|
||||
let default_loc = parenthesized_range(
|
||||
default.into(),
|
||||
parameter_with_default.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(default.range());
|
||||
|
||||
// Delete the default value. For example,
|
||||
// - def __post_init__(self, foo: int = 0) -> None: ...
|
||||
// + def __post_init__(self, foo: int) -> None: ...
|
||||
let default_edit = Edit::deletion(parameter.end(), default.end());
|
||||
let default_edit = Edit::deletion(parameter.end(), default_loc.end());
|
||||
|
||||
// Add `dataclasses.InitVar` field to class body.
|
||||
let locator = checker.locator();
|
||||
|
||||
let content = {
|
||||
let default = locator.slice(default_loc);
|
||||
let parameter_name = locator.slice(¶meter.name);
|
||||
let default = locator.slice(default);
|
||||
let line_ending = checker.stylist().line_ending().as_str();
|
||||
|
||||
if let Some(annotation) = ¶meter
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{
|
||||
BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, StringLiteral,
|
||||
BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, PythonVersion, StringLiteral,
|
||||
};
|
||||
use ruff_python_semantic::{Modules, SemanticModel};
|
||||
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
@@ -24,6 +24,29 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
/// Regular expressions should be written
|
||||
/// using raw strings to avoid double escaping.
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// The fix is unsafe if the string/bytes literal contains an escape sequence because the fix alters
|
||||
/// the runtime value of the literal while retaining the regex semantics.
|
||||
///
|
||||
/// For example
|
||||
/// ```python
|
||||
/// # Literal is `1\n2`.
|
||||
/// re.compile("1\n2")
|
||||
///
|
||||
/// # Literal is `1\\n2`, but the regex library will interpret `\\n` and will still match a newline
|
||||
/// # character as before.
|
||||
/// re.compile(r"1\n2")
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix availability
|
||||
/// A fix is not available if either
|
||||
/// * the argument is a string with a (no-op) `u` prefix (e.g., `u"foo"`) as the prefix is
|
||||
/// incompatible with the raw prefix `r`
|
||||
/// * the argument is a string or bytes literal with an escape sequence that has a different
|
||||
/// meaning in the context of a regular expression such as `\b`, which is word boundary or
|
||||
/// backspace in a regex, depending on the context, but always a backspace in string and bytes
|
||||
/// literals.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
@@ -163,20 +186,44 @@ fn check_string(checker: &Checker, literal: &StringLiteral, module: RegexModule,
|
||||
let range = literal.range;
|
||||
let mut diagnostic = checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
|
||||
|
||||
if
|
||||
// The (no-op) `u` prefix is a syntax error when combined with `r`
|
||||
!literal.flags.prefix().is_unicode()
|
||||
// We are looking for backslash characters
|
||||
// in the raw source code here, because `\n`
|
||||
// gets converted to a single character already
|
||||
// at the lexing stage.
|
||||
&&!checker.locator().slice(literal.range()).contains('\\')
|
||||
{
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
"r".to_string(),
|
||||
literal.range().start(),
|
||||
)));
|
||||
let Some(applicability) = raw_string_applicability(checker, literal) else {
|
||||
return;
|
||||
};
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::insertion("r".to_string(), literal.range().start()),
|
||||
applicability,
|
||||
));
|
||||
}
|
||||
|
||||
/// Check how safe it is to prepend the `r` prefix to the string.
|
||||
///
|
||||
/// ## Returns
|
||||
/// * `None` if the prefix cannot be added,
|
||||
/// * `Some(a)` if it can be added with applicability `a`.
|
||||
fn raw_string_applicability(checker: &Checker, literal: &StringLiteral) -> Option<Applicability> {
|
||||
if literal.flags.prefix().is_unicode() {
|
||||
// The (no-op) `u` prefix is a syntax error when combined with `r`
|
||||
return None;
|
||||
}
|
||||
|
||||
if checker.target_version() >= PythonVersion::PY38 {
|
||||
raw_applicability(checker, literal.range(), |escaped| {
|
||||
matches!(
|
||||
escaped,
|
||||
Some('a' | 'f' | 'n' | 'r' | 't' | 'u' | 'U' | 'v' | 'x' | 'N')
|
||||
)
|
||||
})
|
||||
} else {
|
||||
raw_applicability(checker, literal.range(), |escaped| {
|
||||
matches!(
|
||||
escaped,
|
||||
Some('a' | 'f' | 'n' | 'r' | 't' | 'u' | 'U' | 'v' | 'x')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// re.compile("\a\f\n\N{Partial Differential}\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
}
|
||||
|
||||
fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, func: &str) {
|
||||
@@ -187,5 +234,53 @@ fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, f
|
||||
let kind = PatternKind::Bytes;
|
||||
let func = func.to_string();
|
||||
let range = literal.range;
|
||||
checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
|
||||
let mut diagnostic = checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
|
||||
|
||||
let Some(applicability) = raw_byte_applicability(checker, literal) else {
|
||||
return;
|
||||
};
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::insertion("r".to_string(), literal.range().start()),
|
||||
applicability,
|
||||
));
|
||||
}
|
||||
|
||||
/// Check how same it is to prepend the `r` prefix to the byte sting.
|
||||
///
|
||||
/// ## Returns
|
||||
/// * `None` if the prefix cannot be added,
|
||||
/// * `Some(a)` if it can be added with applicability `a`.
|
||||
fn raw_byte_applicability(checker: &Checker, literal: &BytesLiteral) -> Option<Applicability> {
|
||||
raw_applicability(checker, literal.range(), |escaped| {
|
||||
matches!(escaped, Some('a' | 'f' | 'n' | 'r' | 't' | 'v' | 'x'))
|
||||
})
|
||||
}
|
||||
|
||||
fn raw_applicability(
|
||||
checker: &Checker,
|
||||
literal_range: TextRange,
|
||||
match_allowed_escape_sequence: impl Fn(Option<char>) -> bool,
|
||||
) -> Option<Applicability> {
|
||||
let mut found_slash = false;
|
||||
let mut chars = checker.locator().slice(literal_range).chars().peekable();
|
||||
while let Some(char) = chars.next() {
|
||||
if char == '\\' {
|
||||
found_slash = true;
|
||||
// Turning `"\uXXXX"` into `r"\uXXXX"` is behaviorally equivalent when passed
|
||||
// to `re`, however, it's not exactly the same runtime value.
|
||||
// Similarly, for the other escape sequences.
|
||||
if !match_allowed_escape_sequence(chars.peek().copied()) {
|
||||
// If the next character is not one of the whitelisted ones, we likely cannot safely turn
|
||||
// this into a raw string.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(if found_slash {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,3 +156,281 @@ RUF033.py:67:59: RUF033 `__post_init__` method with argument defaults
|
||||
| ^^^^^ RUF033
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
RUF033.py:73:41: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
71 | @dataclass
|
||||
72 | class Foo:
|
||||
73 | def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||
| ^^^^^^ RUF033
|
||||
74 | pass
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
70 70 | # https://github.com/astral-sh/ruff/issues/18950
|
||||
71 71 | @dataclass
|
||||
72 72 | class Foo:
|
||||
73 |- def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||
73 |+ bar: InitVar[int] = (x := 1)
|
||||
74 |+ def __post_init__(self, bar: int) -> None:
|
||||
74 75 | pass
|
||||
75 76 |
|
||||
76 77 |
|
||||
|
||||
RUF033.py:81:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
79 | def __post_init__(
|
||||
80 | self,
|
||||
81 | bar: int = (x := 1) # comment
|
||||
| ^^^^^^ RUF033
|
||||
82 | ,
|
||||
83 | baz: int = (y := 2), # comment
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 |
|
||||
77 77 | @dataclass
|
||||
78 78 | class Foo:
|
||||
79 |+ bar: InitVar[int] = (x := 1)
|
||||
79 80 | def __post_init__(
|
||||
80 81 | self,
|
||||
81 |- bar: int = (x := 1) # comment
|
||||
82 |+ bar: int # comment
|
||||
82 83 | ,
|
||||
83 84 | baz: int = (y := 2), # comment
|
||||
84 85 | foo = (a := 1) # comment
|
||||
|
||||
RUF033.py:83:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
81 | bar: int = (x := 1) # comment
|
||||
82 | ,
|
||||
83 | baz: int = (y := 2), # comment
|
||||
| ^^^^^^ RUF033
|
||||
84 | foo = (a := 1) # comment
|
||||
85 | ,
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 |
|
||||
77 77 | @dataclass
|
||||
78 78 | class Foo:
|
||||
79 |+ baz: InitVar[int] = (y := 2)
|
||||
79 80 | def __post_init__(
|
||||
80 81 | self,
|
||||
81 82 | bar: int = (x := 1) # comment
|
||||
82 83 | ,
|
||||
83 |- baz: int = (y := 2), # comment
|
||||
84 |+ baz: int, # comment
|
||||
84 85 | foo = (a := 1) # comment
|
||||
85 86 | ,
|
||||
86 87 | faz = (b := 2), # comment
|
||||
|
||||
RUF033.py:84:16: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
82 | ,
|
||||
83 | baz: int = (y := 2), # comment
|
||||
84 | foo = (a := 1) # comment
|
||||
| ^^^^^^ RUF033
|
||||
85 | ,
|
||||
86 | faz = (b := 2), # comment
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 |
|
||||
77 77 | @dataclass
|
||||
78 78 | class Foo:
|
||||
79 |+ foo: InitVar = (a := 1)
|
||||
79 80 | def __post_init__(
|
||||
80 81 | self,
|
||||
81 82 | bar: int = (x := 1) # comment
|
||||
82 83 | ,
|
||||
83 84 | baz: int = (y := 2), # comment
|
||||
84 |- foo = (a := 1) # comment
|
||||
85 |+ foo # comment
|
||||
85 86 | ,
|
||||
86 87 | faz = (b := 2), # comment
|
||||
87 88 | ) -> None:
|
||||
|
||||
RUF033.py:86:16: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
84 | foo = (a := 1) # comment
|
||||
85 | ,
|
||||
86 | faz = (b := 2), # comment
|
||||
| ^^^^^^ RUF033
|
||||
87 | ) -> None:
|
||||
88 | pass
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 |
|
||||
77 77 | @dataclass
|
||||
78 78 | class Foo:
|
||||
79 |+ faz: InitVar = (b := 2)
|
||||
79 80 | def __post_init__(
|
||||
80 81 | self,
|
||||
81 82 | bar: int = (x := 1) # comment
|
||||
--------------------------------------------------------------------------------
|
||||
83 84 | baz: int = (y := 2), # comment
|
||||
84 85 | foo = (a := 1) # comment
|
||||
85 86 | ,
|
||||
86 |- faz = (b := 2), # comment
|
||||
87 |+ faz, # comment
|
||||
87 88 | ) -> None:
|
||||
88 89 | pass
|
||||
89 90 |
|
||||
|
||||
RUF033.py:95:20: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
93 | def __post_init__(
|
||||
94 | self,
|
||||
95 | bar: int = 1, # comment
|
||||
| ^ RUF033
|
||||
96 | baz: int = 2, # comment
|
||||
97 | ) -> None:
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
90 90 |
|
||||
91 91 | @dataclass
|
||||
92 92 | class Foo:
|
||||
93 |+ bar: InitVar[int] = 1
|
||||
93 94 | def __post_init__(
|
||||
94 95 | self,
|
||||
95 |- bar: int = 1, # comment
|
||||
96 |+ bar: int, # comment
|
||||
96 97 | baz: int = 2, # comment
|
||||
97 98 | ) -> None:
|
||||
98 99 | pass
|
||||
|
||||
RUF033.py:96:20: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
94 | self,
|
||||
95 | bar: int = 1, # comment
|
||||
96 | baz: int = 2, # comment
|
||||
| ^ RUF033
|
||||
97 | ) -> None:
|
||||
98 | pass
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
90 90 |
|
||||
91 91 | @dataclass
|
||||
92 92 | class Foo:
|
||||
93 |+ baz: InitVar[int] = 2
|
||||
93 94 | def __post_init__(
|
||||
94 95 | self,
|
||||
95 96 | bar: int = 1, # comment
|
||||
96 |- baz: int = 2, # comment
|
||||
97 |+ baz: int, # comment
|
||||
97 98 | ) -> None:
|
||||
98 99 | pass
|
||||
99 100 |
|
||||
|
||||
RUF033.py:105:22: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
103 | def __post_init__(
|
||||
104 | self,
|
||||
105 | arg1: int = (1) # comment
|
||||
| ^ RUF033
|
||||
106 | ,
|
||||
107 | arg2: int = ((1)) # comment
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 |
|
||||
101 101 | @dataclass
|
||||
102 102 | class Foo:
|
||||
103 |+ arg1: InitVar[int] = (1)
|
||||
103 104 | def __post_init__(
|
||||
104 105 | self,
|
||||
105 |- arg1: int = (1) # comment
|
||||
106 |+ arg1: int # comment
|
||||
106 107 | ,
|
||||
107 108 | arg2: int = ((1)) # comment
|
||||
108 109 | ,
|
||||
|
||||
RUF033.py:107:23: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
105 | arg1: int = (1) # comment
|
||||
106 | ,
|
||||
107 | arg2: int = ((1)) # comment
|
||||
| ^ RUF033
|
||||
108 | ,
|
||||
109 | arg2: int = (i for i in range(10)) # comment
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 |
|
||||
101 101 | @dataclass
|
||||
102 102 | class Foo:
|
||||
103 |+ arg2: InitVar[int] = ((1))
|
||||
103 104 | def __post_init__(
|
||||
104 105 | self,
|
||||
105 106 | arg1: int = (1) # comment
|
||||
106 107 | ,
|
||||
107 |- arg2: int = ((1)) # comment
|
||||
108 |+ arg2: int # comment
|
||||
108 109 | ,
|
||||
109 110 | arg2: int = (i for i in range(10)) # comment
|
||||
110 111 | ,
|
||||
|
||||
RUF033.py:109:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
107 | arg2: int = ((1)) # comment
|
||||
108 | ,
|
||||
109 | arg2: int = (i for i in range(10)) # comment
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ RUF033
|
||||
110 | ,
|
||||
111 | ) -> None:
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 |
|
||||
101 101 | @dataclass
|
||||
102 102 | class Foo:
|
||||
103 |+ arg2: InitVar[int] = (i for i in range(10))
|
||||
103 104 | def __post_init__(
|
||||
104 105 | self,
|
||||
105 106 | arg1: int = (1) # comment
|
||||
106 107 | ,
|
||||
107 108 | arg2: int = ((1)) # comment
|
||||
108 109 | ,
|
||||
109 |- arg2: int = (i for i in range(10)) # comment
|
||||
110 |+ arg2: int # comment
|
||||
110 111 | ,
|
||||
111 112 | ) -> None:
|
||||
112 113 | pass
|
||||
|
||||
RUF033.py:121:27: RUF033 [*] `__post_init__` method with argument defaults
|
||||
|
|
||||
119 | def __post_init__(
|
||||
120 | self,
|
||||
121 | bar: (int) = (yield from range(5)) # comment
|
||||
| ^^^^^^^^^^^^^^^^^^^ RUF033
|
||||
122 | ,
|
||||
123 | ) -> None:
|
||||
|
|
||||
= help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | def fun_with_python_syntax():
|
||||
117 117 | @dataclass
|
||||
118 118 | class Foo:
|
||||
119 |+ bar: InitVar[int] = (yield from range(5))
|
||||
119 120 | def __post_init__(
|
||||
120 121 | self,
|
||||
121 |- bar: (int) = (yield from range(5)) # comment
|
||||
122 |+ bar: (int) # comment
|
||||
122 123 | ,
|
||||
123 124 | ) -> None:
|
||||
124 125 | ...
|
||||
|
||||
@@ -21,7 +21,7 @@ RUF039.py:5:12: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
7 7 | re.finditer("dou\ble")
|
||||
8 8 | re.fullmatch('''t\riple single''')
|
||||
|
||||
RUF039.py:6:12: RUF039 First argument to `re.findall()` is not raw string
|
||||
RUF039.py:6:12: RUF039 [*] First argument to `re.findall()` is not raw string
|
||||
|
|
||||
4 | # Errors
|
||||
5 | re.compile('single free-spacing', flags=re.X)
|
||||
@@ -32,6 +32,16 @@ RUF039.py:6:12: RUF039 First argument to `re.findall()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 |
|
||||
4 4 | # Errors
|
||||
5 5 | re.compile('single free-spacing', flags=re.X)
|
||||
6 |-re.findall('si\ngle')
|
||||
6 |+re.findall(r'si\ngle')
|
||||
7 7 | re.finditer("dou\ble")
|
||||
8 8 | re.fullmatch('''t\riple single''')
|
||||
9 9 | re.match("""\triple double""")
|
||||
|
||||
RUF039.py:7:13: RUF039 First argument to `re.finditer()` is not raw string
|
||||
|
|
||||
5 | re.compile('single free-spacing', flags=re.X)
|
||||
@@ -43,7 +53,7 @@ RUF039.py:7:13: RUF039 First argument to `re.finditer()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:8:14: RUF039 First argument to `re.fullmatch()` is not raw string
|
||||
RUF039.py:8:14: RUF039 [*] First argument to `re.fullmatch()` is not raw string
|
||||
|
|
||||
6 | re.findall('si\ngle')
|
||||
7 | re.finditer("dou\ble")
|
||||
@@ -54,7 +64,17 @@ RUF039.py:8:14: RUF039 First argument to `re.fullmatch()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:9:10: RUF039 First argument to `re.match()` is not raw string
|
||||
ℹ Unsafe fix
|
||||
5 5 | re.compile('single free-spacing', flags=re.X)
|
||||
6 6 | re.findall('si\ngle')
|
||||
7 7 | re.finditer("dou\ble")
|
||||
8 |-re.fullmatch('''t\riple single''')
|
||||
8 |+re.fullmatch(r'''t\riple single''')
|
||||
9 9 | re.match("""\triple double""")
|
||||
10 10 | re.search('two', 'args')
|
||||
11 11 | re.split("raw", r'second')
|
||||
|
||||
RUF039.py:9:10: RUF039 [*] First argument to `re.match()` is not raw string
|
||||
|
|
||||
7 | re.finditer("dou\ble")
|
||||
8 | re.fullmatch('''t\riple single''')
|
||||
@@ -65,6 +85,16 @@ RUF039.py:9:10: RUF039 First argument to `re.match()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 | re.findall('si\ngle')
|
||||
7 7 | re.finditer("dou\ble")
|
||||
8 8 | re.fullmatch('''t\riple single''')
|
||||
9 |-re.match("""\triple double""")
|
||||
9 |+re.match(r"""\triple double""")
|
||||
10 10 | re.search('two', 'args')
|
||||
11 11 | re.split("raw", r'second')
|
||||
12 12 | re.sub(u'''nicode''', u"f(?i)rst")
|
||||
|
||||
RUF039.py:10:11: RUF039 [*] First argument to `re.search()` is not raw string
|
||||
|
|
||||
8 | re.fullmatch('''t\riple single''')
|
||||
@@ -117,7 +147,7 @@ RUF039.py:12:8: RUF039 First argument to `re.sub()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:13:9: RUF039 First argument to `re.subn()` is not raw bytes literal
|
||||
RUF039.py:13:9: RUF039 [*] First argument to `re.subn()` is not raw bytes literal
|
||||
|
|
||||
11 | re.split("raw", r'second')
|
||||
12 | re.sub(u'''nicode''', u"f(?i)rst")
|
||||
@@ -128,6 +158,16 @@ RUF039.py:13:9: RUF039 First argument to `re.subn()` is not raw bytes literal
|
||||
|
|
||||
= help: Replace with raw bytes literal
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | re.search('two', 'args')
|
||||
11 11 | re.split("raw", r'second')
|
||||
12 12 | re.sub(u'''nicode''', u"f(?i)rst")
|
||||
13 |-re.subn(b"""ytes are""", f"\u006e")
|
||||
13 |+re.subn(rb"""ytes are""", f"\u006e")
|
||||
14 14 |
|
||||
15 15 | regex.compile('single free-spacing', flags=regex.X)
|
||||
16 16 | regex.findall('si\ngle')
|
||||
|
||||
RUF039.py:15:15: RUF039 [*] First argument to `regex.compile()` is not raw string
|
||||
|
|
||||
13 | re.subn(b"""ytes are""", f"\u006e")
|
||||
@@ -149,7 +189,7 @@ RUF039.py:15:15: RUF039 [*] First argument to `regex.compile()` is not raw strin
|
||||
17 17 | regex.finditer("dou\ble")
|
||||
18 18 | regex.fullmatch('''t\riple single''')
|
||||
|
||||
RUF039.py:16:15: RUF039 First argument to `regex.findall()` is not raw string
|
||||
RUF039.py:16:15: RUF039 [*] First argument to `regex.findall()` is not raw string
|
||||
|
|
||||
15 | regex.compile('single free-spacing', flags=regex.X)
|
||||
16 | regex.findall('si\ngle')
|
||||
@@ -159,6 +199,16 @@ RUF039.py:16:15: RUF039 First argument to `regex.findall()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 | re.subn(b"""ytes are""", f"\u006e")
|
||||
14 14 |
|
||||
15 15 | regex.compile('single free-spacing', flags=regex.X)
|
||||
16 |-regex.findall('si\ngle')
|
||||
16 |+regex.findall(r'si\ngle')
|
||||
17 17 | regex.finditer("dou\ble")
|
||||
18 18 | regex.fullmatch('''t\riple single''')
|
||||
19 19 | regex.match("""\triple double""")
|
||||
|
||||
RUF039.py:17:16: RUF039 First argument to `regex.finditer()` is not raw string
|
||||
|
|
||||
15 | regex.compile('single free-spacing', flags=regex.X)
|
||||
@@ -170,7 +220,7 @@ RUF039.py:17:16: RUF039 First argument to `regex.finditer()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:18:17: RUF039 First argument to `regex.fullmatch()` is not raw string
|
||||
RUF039.py:18:17: RUF039 [*] First argument to `regex.fullmatch()` is not raw string
|
||||
|
|
||||
16 | regex.findall('si\ngle')
|
||||
17 | regex.finditer("dou\ble")
|
||||
@@ -181,7 +231,17 @@ RUF039.py:18:17: RUF039 First argument to `regex.fullmatch()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:19:13: RUF039 First argument to `regex.match()` is not raw string
|
||||
ℹ Unsafe fix
|
||||
15 15 | regex.compile('single free-spacing', flags=regex.X)
|
||||
16 16 | regex.findall('si\ngle')
|
||||
17 17 | regex.finditer("dou\ble")
|
||||
18 |-regex.fullmatch('''t\riple single''')
|
||||
18 |+regex.fullmatch(r'''t\riple single''')
|
||||
19 19 | regex.match("""\triple double""")
|
||||
20 20 | regex.search('two', 'args')
|
||||
21 21 | regex.split("raw", r'second')
|
||||
|
||||
RUF039.py:19:13: RUF039 [*] First argument to `regex.match()` is not raw string
|
||||
|
|
||||
17 | regex.finditer("dou\ble")
|
||||
18 | regex.fullmatch('''t\riple single''')
|
||||
@@ -192,6 +252,16 @@ RUF039.py:19:13: RUF039 First argument to `regex.match()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | regex.findall('si\ngle')
|
||||
17 17 | regex.finditer("dou\ble")
|
||||
18 18 | regex.fullmatch('''t\riple single''')
|
||||
19 |-regex.match("""\triple double""")
|
||||
19 |+regex.match(r"""\triple double""")
|
||||
20 20 | regex.search('two', 'args')
|
||||
21 21 | regex.split("raw", r'second')
|
||||
22 22 | regex.sub(u'''nicode''', u"f(?i)rst")
|
||||
|
||||
RUF039.py:20:14: RUF039 [*] First argument to `regex.search()` is not raw string
|
||||
|
|
||||
18 | regex.fullmatch('''t\riple single''')
|
||||
@@ -244,7 +314,7 @@ RUF039.py:22:11: RUF039 First argument to `regex.sub()` is not raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:23:12: RUF039 First argument to `regex.subn()` is not raw bytes literal
|
||||
RUF039.py:23:12: RUF039 [*] First argument to `regex.subn()` is not raw bytes literal
|
||||
|
|
||||
21 | regex.split("raw", r'second')
|
||||
22 | regex.sub(u'''nicode''', u"f(?i)rst")
|
||||
@@ -255,6 +325,16 @@ RUF039.py:23:12: RUF039 First argument to `regex.subn()` is not raw bytes litera
|
||||
|
|
||||
= help: Replace with raw bytes literal
|
||||
|
||||
ℹ Safe fix
|
||||
20 20 | regex.search('two', 'args')
|
||||
21 21 | regex.split("raw", r'second')
|
||||
22 22 | regex.sub(u'''nicode''', u"f(?i)rst")
|
||||
23 |-regex.subn(b"""ytes are""", f"\u006e")
|
||||
23 |+regex.subn(rb"""ytes are""", f"\u006e")
|
||||
24 24 |
|
||||
25 25 | regex.template("""(?m)
|
||||
26 26 | (?:ulti)?
|
||||
|
||||
RUF039.py:25:16: RUF039 [*] First argument to `regex.template()` is not raw string
|
||||
|
|
||||
23 | regex.subn(b"""ytes are""", f"\u006e")
|
||||
@@ -278,3 +358,111 @@ RUF039.py:25:16: RUF039 [*] First argument to `regex.template()` is not raw stri
|
||||
26 26 | (?:ulti)?
|
||||
27 27 | (?=(?<!(?<=(?!l)))
|
||||
28 28 | l(?i:ne)
|
||||
|
||||
RUF039.py:59:12: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
58 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
60 | re.compile("\b") # without fix
|
||||
61 | re.compile("\"") # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
56 56 |
|
||||
57 57 |
|
||||
58 58 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
59 |-re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
59 |+re.compile(r"\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
60 60 | re.compile("\b") # without fix
|
||||
61 61 | re.compile("\"") # without fix
|
||||
62 62 | re.compile("\'") # without fix
|
||||
|
||||
RUF039.py:60:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
58 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
60 | re.compile("\b") # without fix
|
||||
| ^^^^ RUF039
|
||||
61 | re.compile("\"") # without fix
|
||||
62 | re.compile("\'") # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:61:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
|
||||
60 | re.compile("\b") # without fix
|
||||
61 | re.compile("\"") # without fix
|
||||
| ^^^^ RUF039
|
||||
62 | re.compile("\'") # without fix
|
||||
63 | re.compile('\"') # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:62:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
60 | re.compile("\b") # without fix
|
||||
61 | re.compile("\"") # without fix
|
||||
62 | re.compile("\'") # without fix
|
||||
| ^^^^ RUF039
|
||||
63 | re.compile('\"') # without fix
|
||||
64 | re.compile('\'') # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:63:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
61 | re.compile("\"") # without fix
|
||||
62 | re.compile("\'") # without fix
|
||||
63 | re.compile('\"') # without fix
|
||||
| ^^^^ RUF039
|
||||
64 | re.compile('\'') # without fix
|
||||
65 | re.compile("\\") # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:64:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
62 | re.compile("\'") # without fix
|
||||
63 | re.compile('\"') # without fix
|
||||
64 | re.compile('\'') # without fix
|
||||
| ^^^^ RUF039
|
||||
65 | re.compile("\\") # without fix
|
||||
66 | re.compile("\101") # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:65:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
63 | re.compile('\"') # without fix
|
||||
64 | re.compile('\'') # without fix
|
||||
65 | re.compile("\\") # without fix
|
||||
| ^^^^ RUF039
|
||||
66 | re.compile("\101") # without fix
|
||||
67 | re.compile("a\
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:66:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
64 | re.compile('\'') # without fix
|
||||
65 | re.compile("\\") # without fix
|
||||
66 | re.compile("\101") # without fix
|
||||
| ^^^^^^ RUF039
|
||||
67 | re.compile("a\
|
||||
68 | b") # without fix
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039.py:67:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
65 | re.compile("\\") # without fix
|
||||
66 | re.compile("\101") # without fix
|
||||
67 | re.compile("a\
|
||||
| ____________^
|
||||
68 | | b") # without fix
|
||||
| |__^ RUF039
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
@@ -65,7 +65,7 @@ RUF039_concat.py:12:5: RUF039 [*] First argument to `re.findall()` is not raw st
|
||||
14 14 | """
|
||||
15 15 | )
|
||||
|
||||
RUF039_concat.py:26:5: RUF039 First argument to `re.match()` is not raw bytes literal
|
||||
RUF039_concat.py:26:5: RUF039 [*] First argument to `re.match()` is not raw bytes literal
|
||||
|
|
||||
24 | )
|
||||
25 | re.match(
|
||||
@@ -76,6 +76,16 @@ RUF039_concat.py:26:5: RUF039 First argument to `re.match()` is not raw bytes li
|
||||
|
|
||||
= help: Replace with raw bytes literal
|
||||
|
||||
ℹ Safe fix
|
||||
23 23 | f'much?'
|
||||
24 24 | )
|
||||
25 25 | re.match(
|
||||
26 |- b'reak'
|
||||
26 |+ rb'reak'
|
||||
27 27 | br'eak'
|
||||
28 28 | )
|
||||
29 29 | re.search(
|
||||
|
||||
RUF039_concat.py:30:8: RUF039 First argument to `re.search()` is not raw string
|
||||
|
|
||||
28 | )
|
||||
@@ -295,7 +305,7 @@ RUF039_concat.py:52:5: RUF039 [*] First argument to `regex.findall()` is not raw
|
||||
54 54 | """
|
||||
55 55 | )
|
||||
|
||||
RUF039_concat.py:66:5: RUF039 First argument to `regex.match()` is not raw bytes literal
|
||||
RUF039_concat.py:66:5: RUF039 [*] First argument to `regex.match()` is not raw bytes literal
|
||||
|
|
||||
64 | )
|
||||
65 | regex.match(
|
||||
@@ -306,6 +316,16 @@ RUF039_concat.py:66:5: RUF039 First argument to `regex.match()` is not raw bytes
|
||||
|
|
||||
= help: Replace with raw bytes literal
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | f'much?'
|
||||
64 64 | )
|
||||
65 65 | regex.match(
|
||||
66 |- b'reak'
|
||||
66 |+ rb'reak'
|
||||
67 67 | br'eak'
|
||||
68 68 | )
|
||||
69 69 | regex.search(
|
||||
|
||||
RUF039_concat.py:70:8: RUF039 First argument to `regex.search()` is not raw string
|
||||
|
|
||||
68 | )
|
||||
@@ -460,3 +480,223 @@ RUF039_concat.py:78:24: RUF039 [*] First argument to `regex.subn()` is not raw s
|
||||
79 79 |
|
||||
80 80 |
|
||||
81 81 | regex.template(
|
||||
|
||||
RUF039_concat.py:98:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
96 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
97 | re.compile(
|
||||
98 | "["
|
||||
| ^^^ RUF039
|
||||
99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Safe fix
|
||||
95 95 |
|
||||
96 96 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
97 97 | re.compile(
|
||||
98 |- "["
|
||||
98 |+ r"["
|
||||
99 99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
|
||||
RUF039_concat.py:99:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
97 | re.compile(
|
||||
98 | "["
|
||||
99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
96 96 | # https://github.com/astral-sh/ruff/issues/16713
|
||||
97 97 | re.compile(
|
||||
98 98 | "["
|
||||
99 |- "\U0001F600-\U0001F64F" # emoticons
|
||||
99 |+ r"\U0001F600-\U0001F64F" # emoticons
|
||||
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
|
||||
RUF039_concat.py:100:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
98 | "["
|
||||
99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
97 97 | re.compile(
|
||||
98 98 | "["
|
||||
99 99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 |- "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
100 |+ r"\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
|
||||
RUF039_concat.py:101:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 | "\U00002702-\U000027B0"
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
98 98 | "["
|
||||
99 99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 |- "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
101 |+ r"\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
104 104 | "\U000024C2-\U0001F251"
|
||||
|
||||
RUF039_concat.py:102:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
103 | "\U00002702-\U000027B0"
|
||||
104 | "\U000024C2-\U0001F251"
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
99 99 | "\U0001F600-\U0001F64F" # emoticons
|
||||
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 |- "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
102 |+ r"\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
104 104 | "\U000024C2-\U0001F251"
|
||||
105 105 | "\u200d" # zero width joiner
|
||||
|
||||
RUF039_concat.py:103:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 | "\U00002702-\U000027B0"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
104 | "\U000024C2-\U0001F251"
|
||||
105 | "\u200d" # zero width joiner
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 |- "\U00002702-\U000027B0"
|
||||
103 |+ r"\U00002702-\U000027B0"
|
||||
104 104 | "\U000024C2-\U0001F251"
|
||||
105 105 | "\u200d" # zero width joiner
|
||||
106 106 | "\u200c" # zero width non-joiner
|
||||
|
||||
RUF039_concat.py:104:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 | "\U00002702-\U000027B0"
|
||||
104 | "\U000024C2-\U0001F251"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
105 | "\u200d" # zero width joiner
|
||||
106 | "\u200c" # zero width non-joiner
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
104 |- "\U000024C2-\U0001F251"
|
||||
104 |+ r"\U000024C2-\U0001F251"
|
||||
105 105 | "\u200d" # zero width joiner
|
||||
106 106 | "\u200c" # zero width non-joiner
|
||||
107 107 | "\\u200c" # must not be escaped in a raw string
|
||||
|
||||
RUF039_concat.py:105:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
103 | "\U00002702-\U000027B0"
|
||||
104 | "\U000024C2-\U0001F251"
|
||||
105 | "\u200d" # zero width joiner
|
||||
| ^^^^^^^^ RUF039
|
||||
106 | "\u200c" # zero width non-joiner
|
||||
107 | "\\u200c" # must not be escaped in a raw string
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
104 104 | "\U000024C2-\U0001F251"
|
||||
105 |- "\u200d" # zero width joiner
|
||||
105 |+ r"\u200d" # zero width joiner
|
||||
106 106 | "\u200c" # zero width non-joiner
|
||||
107 107 | "\\u200c" # must not be escaped in a raw string
|
||||
108 108 | "]+",
|
||||
|
||||
RUF039_concat.py:106:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
104 | "\U000024C2-\U0001F251"
|
||||
105 | "\u200d" # zero width joiner
|
||||
106 | "\u200c" # zero width non-joiner
|
||||
| ^^^^^^^^ RUF039
|
||||
107 | "\\u200c" # must not be escaped in a raw string
|
||||
108 | "]+",
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
103 103 | "\U00002702-\U000027B0"
|
||||
104 104 | "\U000024C2-\U0001F251"
|
||||
105 105 | "\u200d" # zero width joiner
|
||||
106 |- "\u200c" # zero width non-joiner
|
||||
106 |+ r"\u200c" # zero width non-joiner
|
||||
107 107 | "\\u200c" # must not be escaped in a raw string
|
||||
108 108 | "]+",
|
||||
109 109 | flags=re.UNICODE,
|
||||
|
||||
RUF039_concat.py:107:5: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
105 | "\u200d" # zero width joiner
|
||||
106 | "\u200c" # zero width non-joiner
|
||||
107 | "\\u200c" # must not be escaped in a raw string
|
||||
| ^^^^^^^^^ RUF039
|
||||
108 | "]+",
|
||||
109 | flags=re.UNICODE,
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
RUF039_concat.py:108:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
106 | "\u200c" # zero width non-joiner
|
||||
107 | "\\u200c" # must not be escaped in a raw string
|
||||
108 | "]+",
|
||||
| ^^^^ RUF039
|
||||
109 | flags=re.UNICODE,
|
||||
110 | )
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Safe fix
|
||||
105 105 | "\u200d" # zero width joiner
|
||||
106 106 | "\u200c" # zero width non-joiner
|
||||
107 107 | "\\u200c" # must not be escaped in a raw string
|
||||
108 |- "]+",
|
||||
108 |+ r"]+",
|
||||
109 109 | flags=re.UNICODE,
|
||||
110 110 | )
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF039_py_version_sensitive.py:3:12: RUF039 First argument to `re.compile()` is not raw string
|
||||
|
|
||||
1 | import re
|
||||
2 |
|
||||
3 | re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
|
|
||||
= help: Replace with raw string
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF039_py_version_sensitive.py:3:12: RUF039 [*] First argument to `re.compile()` is not raw string
|
||||
|
|
||||
1 | import re
|
||||
2 |
|
||||
3 | re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
||||
|
|
||||
= help: Replace with raw string
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | import re
|
||||
2 2 |
|
||||
3 |-re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
|
||||
3 |+re.compile(r"\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
|
||||
@@ -708,23 +708,10 @@ pub struct ComparableTString<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
|
||||
// The approach taken below necessarily deviates from the
|
||||
// corresponding implementation for [`ast::FStringValue`].
|
||||
// The reason is that a t-string value is composed of _three_
|
||||
// non-comparable parts: literals, f-string expressions, and
|
||||
// t-string interpolations. Since we have merged the AST nodes
|
||||
// that capture f-string expressions and t-string interpolations
|
||||
// into the shared [`ast::InterpolatedElement`], we must
|
||||
// be careful to distinguish between them here.
|
||||
// We model a [`ComparableTString`] on the actual
|
||||
// [CPython implementation] of a `string.templatelib.Template` object.
|
||||
//
|
||||
// Consequently, we model a [`ComparableTString`] on the actual
|
||||
// [CPython implementation] of a `string.templatelib.Template` object:
|
||||
// it is composed of `strings` and `interpolations`. In CPython,
|
||||
// the `strings` field is a tuple of honest strings (since f-strings
|
||||
// are evaluated). Our `strings` field will house both f-string
|
||||
// expressions and string literals.
|
||||
//
|
||||
// Finally, as in CPython, we must be careful to ensure that the length
|
||||
// As in CPython, we must be careful to ensure that the length
|
||||
// of `strings` is always one more than the length of `interpolations` -
|
||||
// that way we can recover the original reading order by interleaving
|
||||
// starting with `strings`. This is how we can tell the
|
||||
@@ -768,19 +755,6 @@ impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
|
||||
.push(ComparableInterpolatedStringElement::Literal("".into()));
|
||||
}
|
||||
|
||||
fn push_fstring_expression(&mut self, expression: &'a ast::InterpolatedElement) {
|
||||
if let Some(ComparableInterpolatedStringElement::Literal(last_literal)) =
|
||||
self.strings.last()
|
||||
{
|
||||
// Recall that we insert empty strings after
|
||||
// each interpolation. If we encounter an f-string
|
||||
// expression, we replace the empty string with it.
|
||||
if last_literal.is_empty() {
|
||||
self.strings.pop();
|
||||
}
|
||||
}
|
||||
self.strings.push(expression.into());
|
||||
}
|
||||
fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) {
|
||||
self.interpolations.push(expression.into());
|
||||
self.start_new_literal();
|
||||
@@ -789,34 +763,13 @@ impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
|
||||
|
||||
let mut collector = Collector::default();
|
||||
|
||||
for part in value {
|
||||
match part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
collector.push_literal(&string_literal.value);
|
||||
for element in value.elements() {
|
||||
match element {
|
||||
ast::InterpolatedStringElement::Literal(literal) => {
|
||||
collector.push_literal(&literal.value);
|
||||
}
|
||||
ast::TStringPart::TString(fstring) => {
|
||||
for element in &fstring.elements {
|
||||
match element {
|
||||
ast::InterpolatedStringElement::Literal(literal) => {
|
||||
collector.push_literal(&literal.value);
|
||||
}
|
||||
ast::InterpolatedStringElement::Interpolation(interpolation) => {
|
||||
collector.push_tstring_interpolation(interpolation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::TStringPart::FString(fstring) => {
|
||||
for element in &fstring.elements {
|
||||
match element {
|
||||
ast::InterpolatedStringElement::Literal(literal) => {
|
||||
collector.push_literal(&literal.value);
|
||||
}
|
||||
ast::InterpolatedStringElement::Interpolation(expression) => {
|
||||
collector.push_fstring_expression(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::InterpolatedStringElement::Interpolation(interpolation) => {
|
||||
collector.push_tstring_interpolation(interpolation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ pub enum StringLikePartIter<'a> {
|
||||
String(std::slice::Iter<'a, ast::StringLiteral>),
|
||||
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
|
||||
FString(std::slice::Iter<'a, ast::FStringPart>),
|
||||
TString(std::slice::Iter<'a, ast::TStringPart>),
|
||||
TString(std::slice::Iter<'a, ast::TString>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StringLikePartIter<'a> {
|
||||
@@ -339,16 +339,7 @@ impl<'a> Iterator for StringLikePartIter<'a> {
|
||||
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
|
||||
}
|
||||
}
|
||||
StringLikePartIter::TString(inner) => {
|
||||
let part = inner.next()?;
|
||||
match part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
StringLikePart::String(string_literal)
|
||||
}
|
||||
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
|
||||
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
|
||||
}
|
||||
}
|
||||
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next()?),
|
||||
};
|
||||
|
||||
Some(part)
|
||||
@@ -378,16 +369,7 @@ impl DoubleEndedIterator for StringLikePartIter<'_> {
|
||||
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
|
||||
}
|
||||
}
|
||||
StringLikePartIter::TString(inner) => {
|
||||
let part = inner.next_back()?;
|
||||
match part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
StringLikePart::String(string_literal)
|
||||
}
|
||||
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
|
||||
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
|
||||
}
|
||||
}
|
||||
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next_back()?),
|
||||
};
|
||||
|
||||
Some(part)
|
||||
|
||||
@@ -1274,6 +1274,7 @@ impl Truthiness {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
Expr::TString(_) => Self::Truthy,
|
||||
Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Set(ast::ExprSet { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
@@ -1362,6 +1363,7 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
Expr::EllipsisLiteral(_) => true,
|
||||
Expr::List(_) => true,
|
||||
Expr::Tuple(_) => true,
|
||||
Expr::TString(_) => true,
|
||||
|
||||
// These expressions must resolve to the inner expression.
|
||||
Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
|
||||
@@ -1386,7 +1388,6 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
// These literals may or may not be empty.
|
||||
Expr::FString(f_string) => is_non_empty_f_string(f_string),
|
||||
// These literals may or may not be empty.
|
||||
Expr::TString(f_string) => is_non_empty_t_string(f_string),
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
|
||||
}
|
||||
@@ -1403,76 +1404,6 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
|
||||
/// f-string expression, or `false` if the expression may resolve to an empty string.
|
||||
fn is_non_empty_t_string(expr: &ast::ExprTString) -> bool {
|
||||
fn inner(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
// When stringified, these expressions are always non-empty.
|
||||
Expr::Lambda(_) => true,
|
||||
Expr::Dict(_) => true,
|
||||
Expr::Set(_) => true,
|
||||
Expr::ListComp(_) => true,
|
||||
Expr::SetComp(_) => true,
|
||||
Expr::DictComp(_) => true,
|
||||
Expr::Compare(_) => true,
|
||||
Expr::NumberLiteral(_) => true,
|
||||
Expr::BooleanLiteral(_) => true,
|
||||
Expr::NoneLiteral(_) => true,
|
||||
Expr::EllipsisLiteral(_) => true,
|
||||
Expr::List(_) => true,
|
||||
Expr::Tuple(_) => true,
|
||||
|
||||
// These expressions must resolve to the inner expression.
|
||||
Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
|
||||
Expr::Named(ast::ExprNamed { value, .. }) => inner(value),
|
||||
|
||||
// These expressions are complex. We can't determine whether they're empty or not.
|
||||
Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
|
||||
Expr::BinOp(ast::ExprBinOp { .. }) => false,
|
||||
Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
|
||||
Expr::Generator(_) => false,
|
||||
Expr::Await(_) => false,
|
||||
Expr::Yield(_) => false,
|
||||
Expr::YieldFrom(_) => false,
|
||||
Expr::Call(_) => false,
|
||||
Expr::Attribute(_) => false,
|
||||
Expr::Subscript(_) => false,
|
||||
Expr::Starred(_) => false,
|
||||
Expr::Name(_) => false,
|
||||
Expr::Slice(_) => false,
|
||||
Expr::IpyEscapeCommand(_) => false,
|
||||
|
||||
// These literals may or may not be empty.
|
||||
Expr::FString(f_string) => is_non_empty_f_string(f_string),
|
||||
// These literals may or may not be empty.
|
||||
Expr::TString(t_string) => is_non_empty_t_string(t_string),
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
expr.value.iter().any(|part| match part {
|
||||
ast::TStringPart::Literal(string_literal) => !string_literal.is_empty(),
|
||||
ast::TStringPart::TString(t_string) => {
|
||||
t_string.elements.iter().all(|element| match element {
|
||||
ast::InterpolatedStringElement::Literal(string_literal) => {
|
||||
!string_literal.is_empty()
|
||||
}
|
||||
ast::InterpolatedStringElement::Interpolation(t_string) => {
|
||||
inner(&t_string.expression)
|
||||
}
|
||||
})
|
||||
}
|
||||
ast::TStringPart::FString(f_string) => {
|
||||
f_string.elements.iter().all(|element| match element {
|
||||
InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
|
||||
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the expression definitely resolves to the empty string, when used as an f-string
|
||||
/// expression.
|
||||
fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
|
||||
@@ -29,6 +29,10 @@ impl Name {
|
||||
Self(compact_str::CompactString::const_new(name))
|
||||
}
|
||||
|
||||
pub fn shrink_to_fit(&mut self) {
|
||||
self.0.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
@@ -171,18 +171,8 @@ impl ast::ExprTString {
|
||||
node_index: _,
|
||||
} = self;
|
||||
|
||||
for t_string_part in value {
|
||||
match t_string_part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
visitor.visit_string_literal(string_literal);
|
||||
}
|
||||
ast::TStringPart::FString(f_string) => {
|
||||
visitor.visit_f_string(f_string);
|
||||
}
|
||||
ast::TStringPart::TString(t_string) => {
|
||||
visitor.visit_t_string(t_string);
|
||||
}
|
||||
}
|
||||
for t_string in value {
|
||||
visitor.visit_t_string(t_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,8 +597,8 @@ impl ExprTString {
|
||||
/// otherwise.
|
||||
pub const fn as_single_part_tstring(&self) -> Option<&TString> {
|
||||
match &self.value.inner {
|
||||
TStringValueInner::Single(TStringPart::TString(tstring)) => Some(tstring),
|
||||
_ => None,
|
||||
TStringValueInner::Single(tstring) => Some(tstring),
|
||||
TStringValueInner::Concatenated(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +614,7 @@ impl TStringValue {
|
||||
/// Creates a new t-string literal with a single [`TString`] part.
|
||||
pub fn single(value: TString) -> Self {
|
||||
Self {
|
||||
inner: TStringValueInner::Single(TStringPart::TString(value)),
|
||||
inner: TStringValueInner::Single(value),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ impl TStringValue {
|
||||
///
|
||||
/// Panics if `values` has less than 2 elements.
|
||||
/// Use [`TStringValue::single`] instead.
|
||||
pub fn concatenated(values: Vec<TStringPart>) -> Self {
|
||||
pub fn concatenated(values: Vec<TString>) -> Self {
|
||||
assert!(
|
||||
values.len() > 1,
|
||||
"Use `TStringValue::single` to create single-part t-strings"
|
||||
@@ -640,78 +640,52 @@ impl TStringValue {
|
||||
matches!(self.inner, TStringValueInner::Concatenated(_))
|
||||
}
|
||||
|
||||
/// Returns a slice of all the [`TStringPart`]s contained in this value.
|
||||
pub fn as_slice(&self) -> &[TStringPart] {
|
||||
/// Returns a slice of all the [`TString`]s contained in this value.
|
||||
pub fn as_slice(&self) -> &[TString] {
|
||||
match &self.inner {
|
||||
TStringValueInner::Single(part) => std::slice::from_ref(part),
|
||||
TStringValueInner::Concatenated(parts) => parts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of all the [`TStringPart`]s contained in this value.
|
||||
fn as_mut_slice(&mut self) -> &mut [TStringPart] {
|
||||
/// Returns a mutable slice of all the [`TString`]s contained in this value.
|
||||
fn as_mut_slice(&mut self) -> &mut [TString] {
|
||||
match &mut self.inner {
|
||||
TStringValueInner::Single(part) => std::slice::from_mut(part),
|
||||
TStringValueInner::Concatenated(parts) => parts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all the [`TStringPart`]s contained in this value.
|
||||
pub fn iter(&self) -> Iter<TStringPart> {
|
||||
/// Returns an iterator over all the [`TString`]s contained in this value.
|
||||
pub fn iter(&self) -> Iter<TString> {
|
||||
self.as_slice().iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all the [`TStringPart`]s contained in this value
|
||||
/// Returns an iterator over all the [`TString`]s contained in this value
|
||||
/// that allows modification.
|
||||
pub fn iter_mut(&mut self) -> IterMut<TStringPart> {
|
||||
pub fn iter_mut(&mut self) -> IterMut<TString> {
|
||||
self.as_mut_slice().iter_mut()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the [`StringLiteral`] parts contained in this value.
|
||||
///
|
||||
/// Note that this doesn't recurse into the t-string parts. For example,
|
||||
///
|
||||
/// ```python
|
||||
/// "foo" t"bar {x}" "baz" t"qux"
|
||||
/// ```
|
||||
///
|
||||
/// Here, the string literal parts returned would be `"foo"` and `"baz"`.
|
||||
pub fn literals(&self) -> impl Iterator<Item = &StringLiteral> {
|
||||
self.iter().filter_map(|part| part.as_literal())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the [`TString`] parts contained in this value.
|
||||
///
|
||||
/// Note that this doesn't recurse into the t-string parts. For example,
|
||||
///
|
||||
/// ```python
|
||||
/// "foo" t"bar {x}" "baz" t"qux"
|
||||
/// ```
|
||||
///
|
||||
/// Here, the t-string parts returned would be `f"bar {x}"` and `f"qux"`.
|
||||
pub fn t_strings(&self) -> impl Iterator<Item = &TString> {
|
||||
self.iter().filter_map(|part| part.as_t_string())
|
||||
}
|
||||
|
||||
/// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value.
|
||||
///
|
||||
/// An t-string element is what makes up an [`TString`] i.e., it is either a
|
||||
/// An interpolated string element is what makes up an [`TString`] i.e., it is either a
|
||||
/// string literal or an interpolation. In the following example,
|
||||
///
|
||||
/// ```python
|
||||
/// "foo" t"bar {x}" "baz" t"qux"
|
||||
/// t"foo" t"bar {x}" t"baz" t"qux"
|
||||
/// ```
|
||||
///
|
||||
/// The t-string elements returned would be string literal (`"bar "`),
|
||||
/// The interpolated string elements returned would be string literal (`"bar "`),
|
||||
/// interpolation (`x`) and string literal (`"qux"`).
|
||||
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
|
||||
self.t_strings().flat_map(|fstring| fstring.elements.iter())
|
||||
self.iter().flat_map(|tstring| tstring.elements.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a TStringValue {
|
||||
type Item = &'a TStringPart;
|
||||
type IntoIter = Iter<'a, TStringPart>;
|
||||
type Item = &'a TString;
|
||||
type IntoIter = Iter<'a, TString>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
@@ -719,8 +693,8 @@ impl<'a> IntoIterator for &'a TStringValue {
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut TStringValue {
|
||||
type Item = &'a mut TStringPart;
|
||||
type IntoIter = IterMut<'a, TStringPart>;
|
||||
type Item = &'a mut TString;
|
||||
type IntoIter = IterMut<'a, TString>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
@@ -731,43 +705,10 @@ impl<'a> IntoIterator for &'a mut TStringValue {
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
enum TStringValueInner {
|
||||
/// A single t-string i.e., `t"foo"`.
|
||||
///
|
||||
/// This is always going to be `TStringPart::TString` variant which is
|
||||
/// maintained by the `TStringValue::single` constructor.
|
||||
Single(TStringPart),
|
||||
Single(TString),
|
||||
|
||||
/// An implicitly concatenated t-string i.e., `"foo" t"bar {x}"`.
|
||||
Concatenated(Vec<TStringPart>),
|
||||
}
|
||||
|
||||
/// An t-string part which is either a string literal, an f-string,
|
||||
/// or a t-string.
|
||||
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub enum TStringPart {
|
||||
Literal(StringLiteral),
|
||||
FString(FString),
|
||||
TString(TString),
|
||||
}
|
||||
|
||||
impl TStringPart {
|
||||
pub fn quote_style(&self) -> Quote {
|
||||
match self {
|
||||
Self::Literal(string_literal) => string_literal.flags.quote_style(),
|
||||
Self::FString(f_string) => f_string.flags.quote_style(),
|
||||
Self::TString(t_string) => t_string.flags.quote_style(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for TStringPart {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
TStringPart::Literal(string_literal) => string_literal.range(),
|
||||
TStringPart::FString(f_string) => f_string.range(),
|
||||
TStringPart::TString(t_string) => t_string.range(),
|
||||
}
|
||||
}
|
||||
/// An implicitly concatenated t-string i.e., `t"foo" t"bar {x}"`.
|
||||
Concatenated(Vec<TString>),
|
||||
}
|
||||
|
||||
pub trait StringFlags: Copy {
|
||||
@@ -1237,6 +1178,12 @@ pub struct TString {
|
||||
pub flags: TStringFlags,
|
||||
}
|
||||
|
||||
impl TString {
|
||||
pub fn quote_style(&self) -> Quote {
|
||||
self.flags.quote_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TString> for Expr {
|
||||
fn from(payload: TString) -> Self {
|
||||
ExprTString {
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::{
|
||||
self as ast, Alias, AnyParameterRef, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension,
|
||||
Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringPart,
|
||||
InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
|
||||
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TStringPart, TypeParam,
|
||||
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
|
||||
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TypeParam, TypeParamParamSpec,
|
||||
TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
|
||||
};
|
||||
|
||||
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
|
||||
@@ -547,14 +547,8 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
}
|
||||
}
|
||||
Expr::TString(ast::ExprTString { value, .. }) => {
|
||||
for part in value {
|
||||
match part {
|
||||
TStringPart::Literal(string_literal) => {
|
||||
visitor.visit_string_literal(string_literal);
|
||||
}
|
||||
TStringPart::FString(f_string) => visitor.visit_f_string(f_string),
|
||||
TStringPart::TString(t_string) => visitor.visit_t_string(t_string),
|
||||
}
|
||||
for t_string in value {
|
||||
visitor.visit_t_string(t_string);
|
||||
}
|
||||
}
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
|
||||
@@ -533,18 +533,8 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
|
||||
}
|
||||
}
|
||||
Expr::TString(ast::ExprTString { value, .. }) => {
|
||||
for t_string_part in value.iter_mut() {
|
||||
match t_string_part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
visitor.visit_string_literal(string_literal);
|
||||
}
|
||||
ast::TStringPart::FString(f_string) => {
|
||||
visitor.visit_f_string(f_string);
|
||||
}
|
||||
ast::TStringPart::TString(t_string) => {
|
||||
visitor.visit_t_string(t_string);
|
||||
}
|
||||
}
|
||||
for t_string in value.iter_mut() {
|
||||
visitor.visit_t_string(t_string);
|
||||
}
|
||||
}
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
|
||||
@@ -51,28 +51,12 @@ fn concatenated_fstrings_compare_equal() -> Result<(), ParseError> {
|
||||
|
||||
#[test]
|
||||
fn concatenated_tstrings_compare_equal() -> Result<(), ParseError> {
|
||||
let split_contents = r#"t"{foo!r} this" r"\n raw" t" and {bar!s} that""#;
|
||||
let split_contents = r#"t"{foo!r} this" rt"\n raw" t" and {bar!s} that""#;
|
||||
let value_contents = r#"t"{foo!r} this\\n raw and {bar!s} that""#;
|
||||
|
||||
assert_comparable(split_contents, value_contents)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concatenated_f_and_t_strings_interwoven_compare_equal() -> Result<(), ParseError> {
|
||||
let split_contents = r#"f"{foo} this " t"{bar}" "baz""#;
|
||||
let value_contents = r#"f"{foo}" t" this {bar}" "baz""#;
|
||||
|
||||
assert_comparable(split_contents, value_contents)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concatenated_f_and_t_strings_compare_unequal_when_swapped() -> Result<(), ParseError> {
|
||||
let f_then_t_contents = r#"f"{foo!r} this" r"\n raw" t" and {bar!s} that""#;
|
||||
let t_then_f_contents = r#"t"{foo!r} this" r"\n raw" f" and {bar!s} that""#;
|
||||
|
||||
assert_noncomparable(f_then_t_contents, t_then_f_contents)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_strings_literal_order_matters_compare_unequal() -> Result<(), ParseError> {
|
||||
let interp_then_literal_contents = r#"t"{foo}bar""#;
|
||||
@@ -80,11 +64,3 @@ fn t_strings_literal_order_matters_compare_unequal() -> Result<(), ParseError> {
|
||||
|
||||
assert_noncomparable(interp_then_literal_contents, literal_then_interp_contents)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_strings_empty_concat_equal() -> Result<(), ParseError> {
|
||||
let empty_literal = r#""" t"hey{foo}""#;
|
||||
let empty_f_string = r#"f""t"hey{foo}""#;
|
||||
|
||||
assert_comparable(empty_literal, empty_f_string)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ expression: trace
|
||||
- ModModule
|
||||
- StmtExpr
|
||||
- ExprTString
|
||||
- StringLiteral
|
||||
- TString
|
||||
- InterpolatedStringLiteralElement
|
||||
- TString
|
||||
- InterpolatedStringLiteralElement
|
||||
- InterpolatedElement
|
||||
|
||||
@@ -4,7 +4,8 @@ expression: trace
|
||||
---
|
||||
- StmtExpr
|
||||
- ExprTString
|
||||
- StringLiteral
|
||||
- TString
|
||||
- InterpolatedStringLiteralElement
|
||||
- TString
|
||||
- InterpolatedStringLiteralElement
|
||||
- InterpolatedElement
|
||||
|
||||
@@ -148,7 +148,7 @@ fn f_strings() {
|
||||
|
||||
#[test]
|
||||
fn t_strings() {
|
||||
let source = r"'pre' t'foo {bar:.{x}f} baz'";
|
||||
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
|
||||
|
||||
let trace = trace_source_order_visitation(source);
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ fn f_strings() {
|
||||
|
||||
#[test]
|
||||
fn t_strings() {
|
||||
let source = r"'pre' t'foo {bar:.{x}f} baz'";
|
||||
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
|
||||
|
||||
let trace = trace_visitation(source);
|
||||
|
||||
|
||||
@@ -1538,19 +1538,9 @@ impl<'a> Generator<'a> {
|
||||
|
||||
fn unparse_t_string_value(&mut self, value: &ast::TStringValue) {
|
||||
let mut first = true;
|
||||
for t_string_part in value {
|
||||
for t_string in value {
|
||||
self.p_delim(&mut first, " ");
|
||||
match t_string_part {
|
||||
ast::TStringPart::Literal(string_literal) => {
|
||||
self.unparse_string_literal(string_literal);
|
||||
}
|
||||
ast::TStringPart::FString(f_string) => {
|
||||
self.unparse_interpolated_string(&f_string.elements, f_string.flags.into());
|
||||
}
|
||||
ast::TStringPart::TString(t_string) => {
|
||||
self.unparse_interpolated_string(&t_string.elements, t_string.flags.into());
|
||||
}
|
||||
}
|
||||
self.unparse_interpolated_string(&t_string.elements, t_string.flags.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,16 +104,13 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
|
||||
# T-strings
|
||||
##############################################################################
|
||||
|
||||
# Escape `{` and `}` when merging a t-string with a string
|
||||
"a {not_a_variable}" t"b {10}" "c"
|
||||
|
||||
# Join, and break expressions
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
|
||||
expression
|
||||
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
|
||||
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
|
||||
|
||||
# Join, but don't break the expressions
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
|
||||
|
||||
t"test{
|
||||
expression
|
||||
@@ -171,22 +168,11 @@ t"test" tR"test"
|
||||
|
||||
"single" f""""single"""
|
||||
|
||||
"single" t""""single"""
|
||||
t"single" t""""single"""
|
||||
|
||||
b"single" b"""triple"""
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Don't join t-strings and f-strings
|
||||
##############################################################################
|
||||
|
||||
t"{interp}" f"{expr}"
|
||||
|
||||
f"{expr}" t"{interp}"
|
||||
|
||||
f"{expr}" "string" t"{interp}"
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Join strings in with statements
|
||||
##############################################################################
|
||||
|
||||
@@ -345,7 +345,7 @@ a[
|
||||
b
|
||||
] = (
|
||||
t"ccccc{
|
||||
expression}ccccccccccc" "cccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Same but starting with a joined string. They should both result in the same formatting.
|
||||
@@ -361,7 +361,7 @@ a[
|
||||
aaaaaaa,
|
||||
b
|
||||
] = t"ccccc{
|
||||
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
|
||||
|
||||
|
||||
# Split an overlong target, but join the string if it fits
|
||||
@@ -370,7 +370,7 @@ a[
|
||||
b
|
||||
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
t"ccccc{
|
||||
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Split both if necessary and keep multiline
|
||||
@@ -379,66 +379,66 @@ a[
|
||||
b
|
||||
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
t"ccccc{
|
||||
expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
|
||||
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
|
||||
aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings that contain commented expressions
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
|
||||
a # comment
|
||||
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
|
||||
a # comment
|
||||
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings with multiline debug expressions:
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
|
||||
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
|
||||
@@ -499,7 +499,7 @@ a = (
|
||||
)
|
||||
|
||||
logger.error(
|
||||
f"Failed to run task {task} for job"
|
||||
f"Failed to run task {task} for job"
|
||||
f"with id {str(job.id)}" # type: ignore[union-attr]
|
||||
)
|
||||
|
||||
|
||||
@@ -8,21 +8,21 @@ rt"Not-so-tricky \"quote"
|
||||
|
||||
# Regression test for tstrings dropping comments
|
||||
result_f = (
|
||||
'Traceback (most recent call last):\n'
|
||||
t'Traceback (most recent call last):\n'
|
||||
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
# XXX: The following line changes depending on whether the tests
|
||||
# are run through the interactive interpreter or with -m
|
||||
# It also varies depending on the platform (stack size)
|
||||
# Fortunately, we don't care about exactness here, so we use regex
|
||||
r' \[Previous line repeated (\d+) more times\]' '\n'
|
||||
'RecursionError: maximum recursion depth exceeded\n'
|
||||
rt' \[Previous line repeated (\d+) more times\]' t'\n'
|
||||
t'RecursionError: maximum recursion depth exceeded\n'
|
||||
)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ result_f = (
|
||||
(
|
||||
t'{1}'
|
||||
# comment 1
|
||||
''
|
||||
t''
|
||||
)
|
||||
|
||||
(
|
||||
@@ -655,7 +655,7 @@ hello {
|
||||
|
||||
# Implicit concatenated t-string containing quotes
|
||||
_ = (
|
||||
'This string should change its quotes to double quotes'
|
||||
t'This string should change its quotes to double quotes'
|
||||
t'This string uses double quotes in an expression {"it's a quote"}'
|
||||
t'This t-string does not use any quotes.'
|
||||
)
|
||||
|
||||
@@ -110,16 +110,13 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
|
||||
# T-strings
|
||||
##############################################################################
|
||||
|
||||
# Escape `{` and `}` when merging a t-string with a string
|
||||
"a {not_a_variable}" t"b {10}" "c"
|
||||
|
||||
# Join, and break expressions
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
|
||||
expression
|
||||
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
|
||||
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
|
||||
|
||||
# Join, but don't break the expressions
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
|
||||
|
||||
t"test{
|
||||
expression
|
||||
@@ -177,22 +174,11 @@ t"test" tR"test"
|
||||
|
||||
"single" f""""single"""
|
||||
|
||||
"single" t""""single"""
|
||||
t"single" t""""single"""
|
||||
|
||||
b"single" b"""triple"""
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Don't join t-strings and f-strings
|
||||
##############################################################################
|
||||
|
||||
t"{interp}" f"{expr}"
|
||||
|
||||
f"{expr}" t"{interp}"
|
||||
|
||||
f"{expr}" "string" t"{interp}"
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Join strings in with statements
|
||||
##############################################################################
|
||||
@@ -521,9 +507,6 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
|
||||
# T-strings
|
||||
##############################################################################
|
||||
|
||||
# Escape `{` and `}` when merging a t-string with a string
|
||||
t"a {{not_a_variable}}b {10}c"
|
||||
|
||||
# Join, and break expressions
|
||||
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
|
||||
expression
|
||||
@@ -583,22 +566,11 @@ t"test" Rt"test"
|
||||
|
||||
"single" f""""single"""
|
||||
|
||||
"single" t""""single"""
|
||||
t"single" t""""single"""
|
||||
|
||||
b"single" b"""triple"""
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Don't join t-strings and f-strings
|
||||
##############################################################################
|
||||
|
||||
t"{interp}" f"{expr}"
|
||||
|
||||
f"{expr}" t"{interp}"
|
||||
|
||||
f"{expr}" "string" t"{interp}"
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Join strings in with statements
|
||||
##############################################################################
|
||||
@@ -905,7 +877,7 @@ f"aaaaaaaaaaaaaaaa \
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -302,9 +302,12 @@
|
||||
@@ -288,9 +288,12 @@
|
||||
##############################################################################
|
||||
# Use can_omit_optional_parentheses layout to avoid an instability where the formatter
|
||||
# picks the can_omit_optional_parentheses layout when the strings are joined.
|
||||
|
||||
@@ -351,7 +351,7 @@ a[
|
||||
b
|
||||
] = (
|
||||
t"ccccc{
|
||||
expression}ccccccccccc" "cccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Same but starting with a joined string. They should both result in the same formatting.
|
||||
@@ -367,7 +367,7 @@ a[
|
||||
aaaaaaa,
|
||||
b
|
||||
] = t"ccccc{
|
||||
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
|
||||
|
||||
|
||||
# Split an overlong target, but join the string if it fits
|
||||
@@ -376,7 +376,7 @@ a[
|
||||
b
|
||||
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
t"ccccc{
|
||||
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
|
||||
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Split both if necessary and keep multiline
|
||||
@@ -385,66 +385,66 @@ a[
|
||||
b
|
||||
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
t"ccccc{
|
||||
expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
|
||||
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
|
||||
aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
[a,]
|
||||
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
|
||||
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings that contain commented expressions
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
|
||||
a # comment
|
||||
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
|
||||
a # comment
|
||||
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings with multiline debug expressions:
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
|
||||
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ a = (
|
||||
)
|
||||
|
||||
logger.error(
|
||||
f"Failed to run task {task} for job"
|
||||
f"Failed to run task {task} for job"
|
||||
f"with id {str(job.id)}" # type: ignore[union-attr]
|
||||
)
|
||||
|
||||
@@ -909,7 +909,7 @@ a[aaaaaaa, b] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccc" # comme
|
||||
# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit.
|
||||
a[aaaaaaa, b] = (
|
||||
t"ccccc{expression}ccccccccccc"
|
||||
"ccccccccccccccccccccccccccccccccccccccccccc"
|
||||
t"ccccccccccccccccccccccccccccccccccccccccccc"
|
||||
) # comment
|
||||
|
||||
|
||||
@@ -925,7 +925,7 @@ a[
|
||||
aaaaaaa, b
|
||||
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
t"ccccc{expression}cccccccccccccccccccccccccccccccc"
|
||||
"ccccccccccccccccccccccccccccccc"
|
||||
t"ccccccccccccccccccccccccccccccc"
|
||||
) # comment
|
||||
|
||||
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
|
||||
@@ -935,8 +935,8 @@ aaaaaaaaaaaaaaaaaa = (
|
||||
a,
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeeee"
|
||||
"test"
|
||||
t"moreeeeeeeeeeeeeeeeeeee"
|
||||
t"test"
|
||||
) # comment
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
@@ -945,8 +945,8 @@ aaaaaaaaaaaaaaaaaa = (
|
||||
a,
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeeee"
|
||||
"test" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeeee"
|
||||
t"test" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
@@ -955,8 +955,8 @@ aaaaa[aaaaaaaaaaa] = (
|
||||
a,
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeeee"
|
||||
"test"
|
||||
t"moreeeeeeeeeeeeeeeeeeee"
|
||||
t"test"
|
||||
) # comment
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
@@ -965,8 +965,8 @@ aaaaa[aaaaaaaaaaa] = (
|
||||
a,
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeeee"
|
||||
"test" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeeee"
|
||||
t"test" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings that contain commented expressions
|
||||
@@ -976,7 +976,7 @@ aaaaaaaaaaaaaaaaaa = (
|
||||
a # comment
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
@@ -985,38 +985,38 @@ aaaaa[aaaaaaaaaaa] = (
|
||||
a # comment
|
||||
]
|
||||
}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
# Don't inline t-strings with multiline debug expressions:
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
|
||||
b=}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaaaaaaa = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
|
||||
a=}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
aaaaa[aaaaaaaaaaa] = (
|
||||
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
|
||||
=}"
|
||||
"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
t"moreeeeeeeeeeeeeeeeeetest" # comment
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -14,21 +14,21 @@ rt"Not-so-tricky \"quote"
|
||||
|
||||
# Regression test for tstrings dropping comments
|
||||
result_f = (
|
||||
'Traceback (most recent call last):\n'
|
||||
t'Traceback (most recent call last):\n'
|
||||
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
t' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
t' f()\n'
|
||||
# XXX: The following line changes depending on whether the tests
|
||||
# are run through the interactive interpreter or with -m
|
||||
# It also varies depending on the platform (stack size)
|
||||
# Fortunately, we don't care about exactness here, so we use regex
|
||||
r' \[Previous line repeated (\d+) more times\]' '\n'
|
||||
'RecursionError: maximum recursion depth exceeded\n'
|
||||
rt' \[Previous line repeated (\d+) more times\]' t'\n'
|
||||
t'RecursionError: maximum recursion depth exceeded\n'
|
||||
)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ result_f = (
|
||||
(
|
||||
t'{1}'
|
||||
# comment 1
|
||||
''
|
||||
t''
|
||||
)
|
||||
|
||||
(
|
||||
@@ -661,7 +661,7 @@ hello {
|
||||
|
||||
# Implicit concatenated t-string containing quotes
|
||||
_ = (
|
||||
'This string should change its quotes to double quotes'
|
||||
t'This string should change its quotes to double quotes'
|
||||
t'This string uses double quotes in an expression {"it's a quote"}'
|
||||
t'This t-string does not use any quotes.'
|
||||
)
|
||||
@@ -761,22 +761,22 @@ rt"Not-so-tricky \"quote"
|
||||
|
||||
# Regression test for tstrings dropping comments
|
||||
result_f = (
|
||||
"Traceback (most recent call last):\n"
|
||||
t"Traceback (most recent call last):\n"
|
||||
t' File "{__file__}", line {lineno_f + 5}, in _check_recursive_traceback_display\n'
|
||||
" f()\n"
|
||||
t" f()\n"
|
||||
t' File "{__file__}", line {lineno_f + 1}, in f\n'
|
||||
" f()\n"
|
||||
t" f()\n"
|
||||
t' File "{__file__}", line {lineno_f + 1}, in f\n'
|
||||
" f()\n"
|
||||
t" f()\n"
|
||||
t' File "{__file__}", line {lineno_f + 1}, in f\n'
|
||||
" f()\n"
|
||||
t" f()\n"
|
||||
# XXX: The following line changes depending on whether the tests
|
||||
# are run through the interactive interpreter or with -m
|
||||
# It also varies depending on the platform (stack size)
|
||||
# Fortunately, we don't care about exactness here, so we use regex
|
||||
r" \[Previous line repeated (\d+) more times\]"
|
||||
"\n"
|
||||
"RecursionError: maximum recursion depth exceeded\n"
|
||||
rt" \[Previous line repeated (\d+) more times\]"
|
||||
t"\n"
|
||||
t"RecursionError: maximum recursion depth exceeded\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -785,7 +785,7 @@ result_f = (
|
||||
(
|
||||
t"{1}"
|
||||
# comment 1
|
||||
""
|
||||
t""
|
||||
)
|
||||
|
||||
(
|
||||
@@ -1463,7 +1463,7 @@ hello {
|
||||
|
||||
# Implicit concatenated t-string containing quotes
|
||||
_ = (
|
||||
"This string should change its quotes to double quotes"
|
||||
t"This string should change its quotes to double quotes"
|
||||
t"This string uses double quotes in an expression {"it's a quote"}"
|
||||
t"This t-string does not use any quotes."
|
||||
)
|
||||
|
||||
@@ -3,4 +3,3 @@ t"{hey}"
|
||||
t'{there}'
|
||||
t"""what's
|
||||
happening?"""
|
||||
"implicitly"t"concatenated"
|
||||
|
||||
@@ -3,4 +3,3 @@ t"{hey}"
|
||||
t'{there}'
|
||||
t"""what's
|
||||
happening?"""
|
||||
"implicitly"t"concatenated"
|
||||
|
||||
@@ -17,7 +17,7 @@ t"{ foo = !s }"
|
||||
t"{ 1, 2 = }"
|
||||
t'{t"{3.1415=:.1f}":*^20}'
|
||||
|
||||
{"foo " t"bar {x + y} " "baz": 10}
|
||||
{t"foo " t"bar {x + y} " t"baz": 10}
|
||||
match foo:
|
||||
case "one":
|
||||
pass
|
||||
@@ -44,31 +44,18 @@ t"{x=!a}"
|
||||
t"{x:.3f!r =}"
|
||||
t"{x = !r :.3f}"
|
||||
t"{x:.3f=!r}"
|
||||
"hello" t"{x}"
|
||||
t"hello" t"{x}"
|
||||
t"{x}" t"{y}"
|
||||
t"{x}" "world"
|
||||
t"{x}" t"world"
|
||||
t"Invalid args in command: {command, *args}"
|
||||
"foo" t"{x}" "bar"
|
||||
t"foo" t"{x}" t"bar"
|
||||
(
|
||||
t"a"
|
||||
t"b"
|
||||
"c"
|
||||
t"c"
|
||||
rt"d"
|
||||
fr"e"
|
||||
tr"e"
|
||||
)
|
||||
|
||||
# With unicode strings
|
||||
u"foo" t"{bar}" "baz" " some"
|
||||
"foo" t"{bar}" u"baz" " some"
|
||||
"foo" t"{bar}" "baz" u" some"
|
||||
u"foo" t"bar {baz} really" u"bar" "no"
|
||||
|
||||
|
||||
# With f-strings
|
||||
f"{this}" t"{that}"
|
||||
t"{this}"f"{that}"
|
||||
t"{this}" "that" f"{other}"
|
||||
f"one {this} two" "that" t"three {other} four"
|
||||
|
||||
# Nesting
|
||||
t"{f"{t"{this}"}"}"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Deref;
|
||||
|
||||
use bitflags::bitflags;
|
||||
@@ -1256,7 +1255,6 @@ impl<'src> Parser<'src> {
|
||||
// t'{there}'
|
||||
// t"""what's
|
||||
// happening?"""
|
||||
// "implicitly"t"concatenated"
|
||||
|
||||
// test_err template_strings_py313
|
||||
// # parse_options: {"target-version": "3.13"}
|
||||
@@ -1264,7 +1262,6 @@ impl<'src> Parser<'src> {
|
||||
// t'{there}'
|
||||
// t"""what's
|
||||
// happening?"""
|
||||
// "implicitly"t"concatenated"
|
||||
let string_type = StringType::TString(
|
||||
self.parse_interpolated_string(InterpolatedStringKind::TString)
|
||||
.into(),
|
||||
@@ -1281,7 +1278,7 @@ impl<'src> Parser<'src> {
|
||||
|
||||
match strings.len() {
|
||||
// This is not possible as the function was called by matching against a
|
||||
// `String` or `FStringStart` token.
|
||||
// `String`, `FStringStart`, or `TStringStart` token.
|
||||
0 => unreachable!("Expected to parse at least one string"),
|
||||
// We need a owned value, hence the `pop` here.
|
||||
1 => match strings.pop().unwrap() {
|
||||
@@ -1322,58 +1319,84 @@ impl<'src> Parser<'src> {
|
||||
) -> Expr {
|
||||
assert!(strings.len() > 1);
|
||||
|
||||
let mut has_tstring = false;
|
||||
let mut has_fstring = false;
|
||||
let mut byte_literal_count = 0;
|
||||
let mut tstring_count = 0;
|
||||
for string in &strings {
|
||||
match string {
|
||||
StringType::FString(_) => has_fstring = true,
|
||||
StringType::TString(_) => has_tstring = true,
|
||||
StringType::TString(_) => tstring_count += 1,
|
||||
StringType::Bytes(_) => byte_literal_count += 1,
|
||||
StringType::Str(_) => {}
|
||||
}
|
||||
}
|
||||
let has_bytes = byte_literal_count > 0;
|
||||
let has_tstring = tstring_count > 0;
|
||||
|
||||
if has_bytes {
|
||||
match byte_literal_count.cmp(&strings.len()) {
|
||||
Ordering::Less => {
|
||||
// TODO(dhruvmanila): This is not an ideal recovery because the parser
|
||||
// replaces the byte literals with an invalid string literal node. Any
|
||||
// downstream tools can extract the raw bytes from the range.
|
||||
//
|
||||
// We could convert the node into a string and mark it as invalid
|
||||
// and would be clever to mark the type which is fewer in quantity.
|
||||
if byte_literal_count < strings.len() {
|
||||
// TODO(dhruvmanila): This is not an ideal recovery because the parser
|
||||
// replaces the byte literals with an invalid string literal node. Any
|
||||
// downstream tools can extract the raw bytes from the range.
|
||||
//
|
||||
// We could convert the node into a string and mark it as invalid
|
||||
// and would be clever to mark the type which is fewer in quantity.
|
||||
|
||||
// test_err mixed_bytes_and_non_bytes_literals
|
||||
// 'first' b'second'
|
||||
// f'first' b'second'
|
||||
// 'first' f'second' b'third'
|
||||
self.add_error(
|
||||
ParseErrorType::OtherError(
|
||||
"Bytes literal cannot be mixed with non-bytes literals".to_string(),
|
||||
),
|
||||
range,
|
||||
);
|
||||
}
|
||||
// Only construct a byte expression if all the literals are bytes
|
||||
// otherwise, we'll try either string, t-string, or f-string. This is to retain
|
||||
// as much information as possible.
|
||||
Ordering::Equal => {
|
||||
let mut values = Vec::with_capacity(strings.len());
|
||||
for string in strings {
|
||||
values.push(match string {
|
||||
StringType::Bytes(value) => value,
|
||||
_ => unreachable!("Expected `StringType::Bytes`"),
|
||||
});
|
||||
}
|
||||
return Expr::from(ast::ExprBytesLiteral {
|
||||
value: ast::BytesLiteralValue::concatenated(values),
|
||||
range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
// test_err mixed_bytes_and_non_bytes_literals
|
||||
// 'first' b'second'
|
||||
// f'first' b'second'
|
||||
// 'first' f'second' b'third'
|
||||
self.add_error(
|
||||
ParseErrorType::OtherError(
|
||||
"Bytes literal cannot be mixed with non-bytes literals".to_string(),
|
||||
),
|
||||
range,
|
||||
);
|
||||
}
|
||||
// Only construct a byte expression if all the literals are bytes
|
||||
// otherwise, we'll try either string, t-string, or f-string. This is to retain
|
||||
// as much information as possible.
|
||||
else {
|
||||
let mut values = Vec::with_capacity(strings.len());
|
||||
for string in strings {
|
||||
values.push(match string {
|
||||
StringType::Bytes(value) => value,
|
||||
_ => unreachable!("Expected `StringType::Bytes`"),
|
||||
});
|
||||
}
|
||||
Ordering::Greater => unreachable!(),
|
||||
return Expr::from(ast::ExprBytesLiteral {
|
||||
value: ast::BytesLiteralValue::concatenated(values),
|
||||
range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if has_tstring {
|
||||
if tstring_count < strings.len() {
|
||||
self.add_error(
|
||||
ParseErrorType::OtherError(
|
||||
"cannot mix t-string literals with string or bytes literals".to_string(),
|
||||
),
|
||||
range,
|
||||
);
|
||||
}
|
||||
// Only construct a t-string expression if all the literals are t-strings
|
||||
// otherwise, we'll try either string or f-string. This is to retain
|
||||
// as much information as possible.
|
||||
else {
|
||||
let mut values = Vec::with_capacity(strings.len());
|
||||
for string in strings {
|
||||
values.push(match string {
|
||||
StringType::TString(value) => value,
|
||||
_ => unreachable!("Expected `StringType::TString`"),
|
||||
});
|
||||
}
|
||||
return Expr::from(ast::ExprTString {
|
||||
value: ast::TStringValue::concatenated(values),
|
||||
range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1414,36 +1437,17 @@ impl<'src> Parser<'src> {
|
||||
});
|
||||
}
|
||||
|
||||
if has_tstring {
|
||||
let mut parts = Vec::with_capacity(strings.len());
|
||||
for string in strings {
|
||||
match string {
|
||||
StringType::TString(tstring) => parts.push(ast::TStringPart::TString(tstring)),
|
||||
StringType::FString(fstring) => {
|
||||
parts.push(ruff_python_ast::TStringPart::FString(fstring));
|
||||
}
|
||||
StringType::Str(string) => parts.push(ast::TStringPart::Literal(string)),
|
||||
StringType::Bytes(bytes) => parts.push(ast::TStringPart::Literal(
|
||||
ast::StringLiteral::invalid(bytes.range()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
return Expr::from(ast::ExprTString {
|
||||
value: ast::TStringValue::concatenated(parts),
|
||||
range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut parts = Vec::with_capacity(strings.len());
|
||||
for string in strings {
|
||||
match string {
|
||||
StringType::FString(fstring) => parts.push(ast::FStringPart::FString(fstring)),
|
||||
StringType::TString(_) => {
|
||||
unreachable!("expected no tstring parts by this point")
|
||||
}
|
||||
StringType::Str(string) => parts.push(ast::FStringPart::Literal(string)),
|
||||
// Bytes and Template strings are invalid at this point
|
||||
// and stored as invalid string literal parts in the
|
||||
// f-string
|
||||
StringType::TString(tstring) => parts.push(ast::FStringPart::Literal(
|
||||
ast::StringLiteral::invalid(tstring.range()),
|
||||
)),
|
||||
StringType::Bytes(bytes) => parts.push(ast::FStringPart::Literal(
|
||||
ast::StringLiteral::invalid(bytes.range()),
|
||||
)),
|
||||
|
||||
@@ -13,18 +13,16 @@ expression: suite
|
||||
range: 0..3,
|
||||
value: TStringValue {
|
||||
inner: Single(
|
||||
TString(
|
||||
TString {
|
||||
range: 0..3,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [],
|
||||
flags: TStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
TString {
|
||||
range: 0..3,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [],
|
||||
flags: TStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
[
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..18,
|
||||
value: TString(
|
||||
ExprTString {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..18,
|
||||
value: TStringValue {
|
||||
inner: Concatenated(
|
||||
[
|
||||
FString(
|
||||
FString {
|
||||
range: 0..9,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 2..8,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "Hello ",
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
TString(
|
||||
TString {
|
||||
range: 10..18,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 12..17,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "world",
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: TStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
ParseError {
|
||||
error: OtherError(
|
||||
"cannot mix t-string literals with string or bytes literals",
|
||||
),
|
||||
location: 0..18,
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
[
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..22,
|
||||
value: TString(
|
||||
ExprTString {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..22,
|
||||
value: TStringValue {
|
||||
inner: Concatenated(
|
||||
[
|
||||
FString(
|
||||
FString {
|
||||
range: 0..9,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 2..8,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "Hello ",
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
TString(
|
||||
TString {
|
||||
range: 10..18,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 12..17,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "world",
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: TStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
Literal(
|
||||
StringLiteral {
|
||||
range: 19..22,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "!",
|
||||
flags: StringLiteralFlags {
|
||||
quote_style: Single,
|
||||
prefix: Empty,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
ParseError {
|
||||
error: OtherError(
|
||||
"cannot mix t-string literals with string or bytes literals",
|
||||
),
|
||||
location: 0..22,
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
[
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..17,
|
||||
value: TString(
|
||||
ExprTString {
|
||||
node_index: AtomicNodeIndex(..),
|
||||
range: 0..17,
|
||||
value: TStringValue {
|
||||
inner: Concatenated(
|
||||
[
|
||||
Literal(
|
||||
StringLiteral {
|
||||
range: 0..8,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "Hello ",
|
||||
flags: StringLiteralFlags {
|
||||
quote_style: Single,
|
||||
prefix: Empty,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
TString(
|
||||
TString {
|
||||
range: 9..17,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 11..16,
|
||||
node_index: AtomicNodeIndex(..),
|
||||
value: "world",
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: TStringFlags {
|
||||
quote_style: Single,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/string.rs
|
||||
expression: suite
|
||||
---
|
||||
ParseError {
|
||||
error: OtherError(
|
||||
"cannot mix t-string literals with string or bytes literals",
|
||||
),
|
||||
location: 0..17,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user