Compare commits

..

1 Commits

Author SHA1 Message Date
David Peter
abf676ea56 [ty] Ignoring 'apprise' for ecosystem checks 2025-07-25 13:39:19 +02:00
406 changed files with 10155 additions and 15915 deletions

View File

@@ -429,7 +429,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -38,7 +38,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -65,7 +65,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -1,109 +0,0 @@
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

View File

@@ -1,97 +0,0 @@
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

View File

@@ -81,7 +81,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.5
rev: v0.12.4
hooks:
- id: ruff-format
- id: ruff-check

28
Cargo.lock generated
View File

@@ -530,7 +530,7 @@ dependencies = [
"ciborium",
"clap",
"codspeed",
"criterion-plot 0.5.0",
"criterion-plot",
"is-terminal",
"itertools 0.10.5",
"num-traits",
@@ -713,15 +713,15 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.7.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot 0.6.0",
"criterion-plot",
"itertools 0.13.0",
"num-traits",
"oorandom",
@@ -742,16 +742,6 @@ 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"
@@ -1161,9 +1151,9 @@ dependencies = [
[[package]]
name = "get-size-derive2"
version = "0.6.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
dependencies = [
"attribute-derive",
"quote",
@@ -1172,9 +1162,9 @@ dependencies = [
[[package]]
name = "get-size2"
version = "0.6.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
dependencies = [
"compact_str",
"get-size-derive2",
@@ -2891,7 +2881,6 @@ dependencies = [
"tracing",
"tracing-subscriber",
"ty_static",
"unicode-width 0.2.1",
"web-time",
"zip",
]
@@ -4316,6 +4305,7 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thin-vec",
"thiserror 2.0.12",
"tracing",
"ty_python_semantic",

View File

@@ -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.7.0", default-features = false }
criterion = { version = "0.6.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.6.0", features = [
get-size2 = { version = "0.5.0", features = [
"derive",
"smallvec",
"hashbrown",
@@ -166,6 +166,7 @@ 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" }

View File

@@ -279,7 +279,6 @@ mod test {
TextEmitter::default()
.with_show_fix_status(true)
.with_color(false)
.emit(
&mut output,
&diagnostics.inner,

View File

@@ -57,40 +57,33 @@ 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.pyi"
],
"ruff/e.py": [],
"ruff/e.pyi": []
}
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": []
}
----- stderr -----
"#);
----- stderr -----
"###);
});
Ok(())

View File

@@ -42,7 +42,6 @@ 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]

View File

@@ -6,9 +6,7 @@ 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, ceil_char_boundary,
};
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
use crate::{Db, files::File};
mod render;

View File

@@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::path::Path;
@@ -8,7 +7,7 @@ use ruff_annotate_snippets::{
};
use ruff_notebook::{Notebook, NotebookIndex};
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_text_size::{TextRange, TextSize};
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
use crate::{
@@ -521,7 +520,7 @@ impl<'r> RenderableSnippets<'r> {
#[derive(Debug)]
struct RenderableSnippet<'r> {
/// The actual snippet text.
snippet: Cow<'r, str>,
snippet: &'r str,
/// The absolute line number corresponding to where this
/// snippet begins.
line_start: OneIndexed,
@@ -581,13 +580,6 @@ 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,
@@ -598,7 +590,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(
@@ -828,230 +820,6 @@ 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 {
@@ -2591,7 +2359,7 @@ watermelon
}
/// Returns a builder for tersely constructing diagnostics.
pub(super) fn builder(
fn builder(
&mut self,
identifier: &'static str,
severity: Severity,
@@ -2658,7 +2426,7 @@ watermelon
///
/// See the docs on `TestEnvironment::span` for the meaning of
/// `path`, `line_offset_start` and `line_offset_end`.
pub(super) fn primary(
fn primary(
mut self,
path: &str,
line_offset_start: &str,

View File

@@ -1,8 +1,8 @@
#[cfg(test)]
mod tests {
use crate::diagnostic::{
DiagnosticFormat, Severity,
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
DiagnosticFormat,
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
};
#[test]
@@ -63,118 +63,4 @@ 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(())
}
}

View File

@@ -42,11 +42,13 @@ impl ModuleImports {
// Resolve the imports.
let mut resolved_imports = ModuleImports::default();
for import in imports {
for resolved in Resolver::new(db).resolve(import) {
if let Some(path) = resolved.as_system_path() {
resolved_imports.insert(path.to_path_buf());
}
}
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());
}
Ok(resolved_imports)

View File

@@ -1,5 +1,5 @@
use ruff_db::files::FilePath;
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
use ty_python_semantic::resolve_module;
use crate::ModuleDb;
use crate::collector::CollectedImport;
@@ -16,67 +16,24 @@ impl<'a> Resolver<'a> {
}
/// Resolve the [`CollectedImport`] into a [`FilePath`].
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
match import {
CollectedImport::Import(import) => {
// 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()
let module = resolve_module(self.db, &import)?;
Some(module.file(self.db)?.path(self.db))
}
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));
// 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();
let module = resolve_module(self.db, &import).or_else(|| {
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
std::iter::once(file)
.chain(std::iter::once(source_file))
.flatten()
resolve_module(self.db, &parent?)
})?;
Some(module.file(self.db)?.path(self.db))
}
}
}
/// 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))
}
}

View File

@@ -25,5 +25,5 @@ def my_func():
# t-strings - all ok
t"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"
"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"

View File

@@ -94,7 +94,7 @@ except Exception:
logging.error("...", exc_info=True)
from logging import critical, error, exception
from logging import error, exception
try:
pass
@@ -114,23 +114,6 @@ 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:
@@ -142,13 +125,6 @@ try:
except Exception:
error("...", exc_info=True)
try:
pass
except Exception:
critical("...", exc_info=True)
try:
...
except Exception as e:

View File

@@ -650,17 +650,3 @@ 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

View File

@@ -289,19 +289,4 @@ def f():
i = "xyz"
result = []
for i in range(3):
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)
result.append((x for x in [i]))

View File

@@ -143,23 +143,3 @@ 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

View File

@@ -55,12 +55,3 @@ _ = 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"))

View File

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

View File

@@ -16,6 +16,7 @@ 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;
@@ -26,6 +27,7 @@ pub(crate) fn check_tokens(
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
settings: &LinterSettings,
source_type: PySourceType,
cell_offsets: Option<&CellOffsets>,
context: &mut LintContext,
@@ -40,8 +42,15 @@ pub(crate) fn check_tokens(
Rule::BlankLinesAfterFunctionOrClass,
Rule::BlankLinesBeforeNestedDefinition,
]) {
BlankLinesChecker::new(locator, stylist, source_type, cell_offsets, context)
.check_lines(tokens);
BlankLinesChecker::new(
locator,
stylist,
settings,
source_type,
cell_offsets,
context,
)
.check_lines(tokens);
}
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
@@ -54,12 +63,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);
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
}
}
if context.is_rule_enabled(Rule::CommentedOutCode) {
eradicate::rules::commented_out_code(context, locator, comment_ranges);
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
}
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
@@ -101,7 +110,7 @@ pub(crate) fn check_tokens(
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]) {
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer);
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
}
if context.any_rule_enabled(&[

View File

@@ -188,6 +188,7 @@ pub fn check_path(
locator,
indexer,
stylist,
settings,
source_type,
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
&mut context,
@@ -472,7 +473,7 @@ pub fn lint_only(
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
);
}
@@ -583,7 +584,7 @@ pub fn lint_fix<'a>(
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
);
}

View File

@@ -118,6 +118,86 @@ 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 {

View File

@@ -6,13 +6,12 @@ use bitflags::bitflags;
use colored::Colorize;
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
};
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
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};
@@ -77,12 +76,6 @@ 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 {
@@ -377,8 +370,9 @@ 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 = ceil_char_boundary(&self.text, start + TextSize::from(1));
let end = locator.ceil_char_boundary(start + TextSize::from(1));
SourceCode {
annotation_range: TextRange::new(start, end),
..self

View File

@@ -225,8 +225,3 @@ 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()
}

View File

@@ -5,6 +5,7 @@ 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;
@@ -50,6 +51,7 @@ 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.
@@ -63,9 +65,7 @@ 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, &context.settings().task_tags[..])
{
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
if let Some(mut diagnostic) =
context.report_diagnostic_if_enabled(CommentedOutCode, range)
{

View File

@@ -47,10 +47,9 @@ use crate::checkers::ast::Checker;
/// raise
/// ```
///
/// 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:
/// 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:
/// ```python
/// try:
/// foo()
@@ -202,7 +201,7 @@ impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
) {
if match attr.as_str() {
"exception" => true,
"error" | "critical" => arguments
"error" => arguments
.find_keyword("exc_info")
.is_some_and(|keyword| is_const_true(&keyword.value)),
_ => false,
@@ -215,7 +214,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" | "critical"] => arguments
["logging", "error"] => arguments
.find_keyword("exc_info")
.is_some_and(|keyword| is_const_true(&keyword.value)),
_ => false,

View File

@@ -1,5 +1,6 @@
---
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`
|
@@ -120,30 +121,3 @@ 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)
|

View File

@@ -27,23 +27,4 @@ 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(())
}
}

View File

@@ -5,8 +5,6 @@ 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};
@@ -26,8 +24,6 @@ enum TokenType {
Def,
For,
Lambda,
Class,
Type,
Irrelevant,
}
@@ -73,8 +69,6 @@ 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.
@@ -104,8 +98,6 @@ 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".
@@ -298,7 +290,7 @@ pub(crate) fn trailing_commas(
}
// Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
let context = update_context(token, prev, prev_prev, &mut stack);
check_token(token, prev, prev_prev, context, locator, lint_context);
@@ -334,7 +326,6 @@ 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,]`.
@@ -417,7 +408,6 @@ 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) {
@@ -427,17 +417,6 @@ 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)

View File

@@ -1,30 +0,0 @@
---
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

View File

@@ -11,6 +11,7 @@ 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
@@ -107,15 +108,13 @@ 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
&& (context
.settings()
.flake8_implicit_str_concat
.allow_multiline
&& (settings.flake8_implicit_str_concat.allow_multiline
|| token.kind() != TokenKind::NonLogicalNewline)
})
.tuple_windows()

View File

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

View File

@@ -263,7 +263,5 @@ 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

View File

@@ -612,8 +612,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]))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
293 |
294 | G_INDEX = None
|
= help: Replace for loop with list comprehension
@@ -625,6 +623,3 @@ 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():

View File

@@ -21,6 +21,7 @@ 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.
@@ -693,12 +694,14 @@ 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>,
@@ -709,6 +712,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
source_type,
cell_offsets,
context,
settings,
}
}
@@ -729,7 +733,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
let line_preprocessor = LinePreprocessor::new(
tokens,
self.locator,
self.context.settings().tab_size,
self.settings.tab_size,
self.cell_offsets,
);
@@ -875,8 +879,7 @@ 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.context.settings().isort.lines_after_imports)
.unwrap_or(max_lines_level)
u32::try_from(self.settings.isort.lines_after_imports).unwrap_or(max_lines_level)
} else {
max_lines_level
}
@@ -938,10 +941,8 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
(LogicalLineKind::Import, Follows::FromImport)
| (LogicalLineKind::FromImport, Follows::Import)
) {
max_lines_level.max(
u32::try_from(self.context.settings().isort.lines_between_types)
.unwrap_or(u32::MAX),
)
max_lines_level
.max(u32::try_from(self.settings.isort.lines_between_types).unwrap_or(u32::MAX))
} else {
expected_blank_lines_before_definition
};

View File

@@ -6,9 +6,8 @@ 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
@@ -36,14 +35,10 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
/// 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)
@@ -52,27 +47,25 @@ enum SliceBoundary {
Last,
}
impl AlwaysFixableViolation for MissingMaxsplitArg {
impl Violation for MissingMaxsplitArg {
#[derive_message_formats]
fn message(&self) -> String {
let MissingMaxsplitArg {
actual_split_type: _,
suggested_split_type,
} = self;
format!("Replace with `{suggested_split_type}(..., maxsplit=1)`.")
}
fn fix_title(&self) -> String {
let MissingMaxsplitArg {
index,
actual_split_type,
suggested_split_type,
} = self;
let suggested_split_type = match index {
SliceBoundary::First => "split",
SliceBoundary::Last => "rsplit",
};
if actual_split_type == suggested_split_type {
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
} else {
format!("Use `str.{suggested_split_type}()` and pass `maxsplit=1`")
format!(
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
)
}
}
}
@@ -130,8 +123,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
};
// Check the function is "split" or "rsplit"
let actual_split_type = attr.as_str();
if !matches!(actual_split_type, "split" | "rsplit") {
let attr = attr.as_str();
if !matches!(attr, "split" | "rsplit") {
return;
}
@@ -168,48 +161,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
}
}
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(
checker.report_diagnostic(
MissingMaxsplitArg {
actual_split_type: actual_split_type.to_string(),
suggested_split_type: suggested_split_type.to_string(),
index: slice_boundary,
actual_split_type: attr.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
},
));
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
12 | # Errors
13 | ## Test split called directly on string literal
@@ -10,19 +10,8 @@ missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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()`
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)`.
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
13 | ## Test split called directly on string literal
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
@@ -31,19 +20,8 @@ missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
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`
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)`.
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
@@ -51,19 +29,8 @@ missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
@@ -72,19 +39,8 @@ missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
18 |
19 | ## Test split called on string variable
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
19 | ## Test split called on string variable
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
@@ -92,19 +48,8 @@ missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
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)`.
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
19 | ## Test split called on string variable
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
@@ -113,19 +58,8 @@ missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` 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)`.
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
@@ -133,19 +67,8 @@ missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^ PLC0207
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -154,19 +77,8 @@ missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
24 |
25 | ## Test split called on class attribute
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
25 | ## Test split called on class attribute
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
@@ -174,19 +86,8 @@ missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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()`
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)`.
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
25 | ## Test split called on class attribute
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
@@ -195,19 +96,8 @@ missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
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`
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)`.
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
@@ -215,19 +105,8 @@ missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -236,19 +115,8 @@ missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
30 |
31 | ## Test split called on sliced string
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
31 | ## Test split called on sliced string
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -256,19 +124,8 @@ missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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()`
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)`.
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
31 | ## Test split called on sliced string
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -277,19 +134,8 @@ missing_maxsplit_arg.py:33: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]
|
= help: 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)`.
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -298,19 +144,8 @@ missing_maxsplit_arg.py:34: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]
|
= help: Pass `maxsplit=1` into `str.split()`
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)`.
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
@@ -319,19 +154,8 @@ missing_maxsplit_arg.py:35: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]
|
= help: Use `str.rsplit()` 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)`.
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
@@ -340,19 +164,8 @@ missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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`
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)`.
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
@@ -360,19 +173,8 @@ missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
@@ -381,19 +183,8 @@ missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
39 |
40 | ## Test sep given as named argument
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
40 | ## Test sep given as named argument
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
@@ -401,19 +192,8 @@ missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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()`
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)`.
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
40 | ## Test sep given as named argument
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
@@ -422,19 +202,8 @@ missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
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`
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)`.
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
@@ -442,19 +211,8 @@ missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
@@ -463,19 +221,8 @@ missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
45 |
46 | ## Special cases
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
46 | ## Special cases
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
@@ -483,19 +230,8 @@ missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
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()`
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)`.
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
46 | ## Special cases
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
@@ -503,19 +239,8 @@ missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` 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)`.
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
@@ -524,19 +249,8 @@ missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
50 |
51 | ## Test class attribute named split
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
51 | ## Test class attribute named split
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
@@ -544,19 +258,8 @@ missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
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)`.
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
51 | ## Test class attribute named split
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
@@ -565,19 +268,8 @@ missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` 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)`.
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
@@ -585,19 +277,8 @@ missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
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)`.
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -606,37 +287,15 @@ missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
56 |
57 | ## Test unpacked dict literal kwargs
|
= help: Pass `maxsplit=1` into `str.rsplit()`
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)`.
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
57 | ## Test unpacked dict literal kwargs
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
= help: 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)`.
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
177 | # Errors
178 | kwargs_without_maxsplit = {"seq": ","}
@@ -645,19 +304,8 @@ missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
= help: 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)`.
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
@@ -666,29 +314,11 @@ missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
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()`
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)`.
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
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

View File

@@ -104,13 +104,6 @@ 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;
};

View File

@@ -180,18 +180,18 @@ fn is_valid_argument_type(
typing::is_float(binding, semantic),
)
})
.unwrap_or((false, false))
.unwrap_or_default()
} else {
(false, false)
};
match (method_name, constructor) {
// Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap)
// Decimal.from_float accepts int, bool, float
(MethodName::FromFloat, Constructor::Decimal) => match resolved_type {
ResolvedPythonType::Atom(PythonType::Number(
NumberLike::Integer | NumberLike::Bool,
NumberLike::Integer | NumberLike::Bool | NumberLike::Float,
)) => true,
ResolvedPythonType::Unknown => is_int,
ResolvedPythonType::Unknown => is_int || is_float,
_ => false,
},
// Fraction.from_float accepts int, bool, float
@@ -286,8 +286,10 @@ fn handle_non_finite_float_special_case(
let [float_arg] = arguments.args.as_ref() else {
return None;
};
let normalized = as_non_finite_float_string_literal(float_arg)?;
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
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})");
Some(Edit::range_replacement(replacement_text, call.range()))
}

View File

@@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct
|
= help: Replace with `Decimal` constructor
Unsafe fix
Safe 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
Unsafe fix
Safe 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
Unsafe fix
Safe 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
Unsafe fix
Safe 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
Unsafe fix
Safe 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("+nan")
24 |+_ = Decimal(" \n+nan \t")
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")
25 |+_ = Decimal(" iNf\n\t ")
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")
26 |+_ = Decimal(" -inF\n \t")
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")
27 |+_ = Decimal(" InfinIty\n\t ")
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")
28 |+_ = Decimal(" -InfinIty\n \t")
29 29 |
30 30 | # Cases with keyword arguments - should produce unsafe fixes
31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2"))
@@ -612,96 +612,3 @@ 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")

View File

@@ -178,10 +178,11 @@ 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, context.settings()) {
candidate.into_diagnostic(Context::Comment, context);
for candidate in ambiguous_unicode_character(text, range, settings) {
candidate.into_diagnostic(Context::Comment, settings, context);
}
}
@@ -341,12 +342,13 @@ impl Candidate {
}
}
fn into_diagnostic(self, context: Context, lint_context: &LintContext) {
if !lint_context
.settings()
.allowed_confusables
.contains(&self.confusable)
{
fn into_diagnostic(
self,
context: Context,
settings: &LinterSettings,
lint_context: &LintContext,
) {
if !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(

View File

@@ -708,10 +708,23 @@ pub struct ComparableTString<'a> {
}
impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
// We model a [`ComparableTString`] on the actual
// [CPython implementation] of a `string.templatelib.Template` object.
// 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.
//
// As in CPython, we must be careful to ensure that the length
// 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
// 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
@@ -755,6 +768,19 @@ 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();
@@ -763,13 +789,34 @@ impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
let mut collector = Collector::default();
for element in value.elements() {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
for part in value {
match part {
ast::TStringPart::Literal(string_literal) => {
collector.push_literal(&string_literal.value);
}
ast::InterpolatedStringElement::Interpolation(interpolation) => {
collector.push_tstring_interpolation(interpolation);
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);
}
}
}
}
}
}

View File

@@ -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::TString>),
TString(std::slice::Iter<'a, ast::TStringPart>),
}
impl<'a> Iterator for StringLikePartIter<'a> {
@@ -339,7 +339,16 @@ impl<'a> Iterator for StringLikePartIter<'a> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next()?),
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),
}
}
};
Some(part)
@@ -369,7 +378,16 @@ impl DoubleEndedIterator for StringLikePartIter<'_> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next_back()?),
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),
}
}
};
Some(part)

View File

@@ -1274,7 +1274,6 @@ impl Truthiness {
Self::Unknown
}
}
Expr::TString(_) => Self::Truthy,
Expr::List(ast::ExprList { elts, .. })
| Expr::Set(ast::ExprSet { elts, .. })
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
@@ -1363,7 +1362,6 @@ 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),
@@ -1388,6 +1386,7 @@ 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(),
}
@@ -1404,6 +1403,76 @@ 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 {

View File

@@ -29,10 +29,6 @@ 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()
}

View File

@@ -171,8 +171,18 @@ impl ast::ExprTString {
node_index: _,
} = self;
for t_string in value {
visitor.visit_t_string(t_string);
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);
}
}
}
}
}

View File

@@ -597,8 +597,8 @@ impl ExprTString {
/// otherwise.
pub const fn as_single_part_tstring(&self) -> Option<&TString> {
match &self.value.inner {
TStringValueInner::Single(tstring) => Some(tstring),
TStringValueInner::Concatenated(_) => None,
TStringValueInner::Single(TStringPart::TString(tstring)) => Some(tstring),
_ => 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(value),
inner: TStringValueInner::Single(TStringPart::TString(value)),
}
}
@@ -625,7 +625,7 @@ impl TStringValue {
///
/// Panics if `values` has less than 2 elements.
/// Use [`TStringValue::single`] instead.
pub fn concatenated(values: Vec<TString>) -> Self {
pub fn concatenated(values: Vec<TStringPart>) -> Self {
assert!(
values.len() > 1,
"Use `TStringValue::single` to create single-part t-strings"
@@ -640,52 +640,78 @@ impl TStringValue {
matches!(self.inner, TStringValueInner::Concatenated(_))
}
/// Returns a slice of all the [`TString`]s contained in this value.
pub fn as_slice(&self) -> &[TString] {
/// Returns a slice of all the [`TStringPart`]s contained in this value.
pub fn as_slice(&self) -> &[TStringPart] {
match &self.inner {
TStringValueInner::Single(part) => std::slice::from_ref(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns a mutable slice of all the [`TString`]s contained in this value.
fn as_mut_slice(&mut self) -> &mut [TString] {
/// Returns a mutable slice of all the [`TStringPart`]s contained in this value.
fn as_mut_slice(&mut self) -> &mut [TStringPart] {
match &mut self.inner {
TStringValueInner::Single(part) => std::slice::from_mut(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns an iterator over all the [`TString`]s contained in this value.
pub fn iter(&self) -> Iter<TString> {
/// Returns an iterator over all the [`TStringPart`]s contained in this value.
pub fn iter(&self) -> Iter<TStringPart> {
self.as_slice().iter()
}
/// Returns an iterator over all the [`TString`]s contained in this value
/// Returns an iterator over all the [`TStringPart`]s contained in this value
/// that allows modification.
pub fn iter_mut(&mut self) -> IterMut<TString> {
pub fn iter_mut(&mut self) -> IterMut<TStringPart> {
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 interpolated string element is what makes up an [`TString`] i.e., it is either a
/// An t-string element is what makes up an [`TString`] i.e., it is either a
/// string literal or an interpolation. In the following example,
///
/// ```python
/// t"foo" t"bar {x}" t"baz" t"qux"
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// The interpolated string elements returned would be string literal (`"bar "`),
/// The t-string elements returned would be string literal (`"bar "`),
/// interpolation (`x`) and string literal (`"qux"`).
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
self.iter().flat_map(|tstring| tstring.elements.iter())
self.t_strings().flat_map(|fstring| fstring.elements.iter())
}
}
impl<'a> IntoIterator for &'a TStringValue {
type Item = &'a TString;
type IntoIter = Iter<'a, TString>;
type Item = &'a TStringPart;
type IntoIter = Iter<'a, TStringPart>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
@@ -693,8 +719,8 @@ impl<'a> IntoIterator for &'a TStringValue {
}
impl<'a> IntoIterator for &'a mut TStringValue {
type Item = &'a mut TString;
type IntoIter = IterMut<'a, TString>;
type Item = &'a mut TStringPart;
type IntoIter = IterMut<'a, TStringPart>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
@@ -705,10 +731,43 @@ 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"`.
Single(TString),
///
/// This is always going to be `TStringPart::TString` variant which is
/// maintained by the `TStringValue::single` constructor.
Single(TStringPart),
/// An implicitly concatenated t-string i.e., `t"foo" t"bar {x}"`.
Concatenated(Vec<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(),
}
}
}
pub trait StringFlags: Copy {
@@ -1178,12 +1237,6 @@ 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 {

View File

@@ -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, TypeParam, TypeParamParamSpec,
TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TStringPart, TypeParam,
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@@ -547,8 +547,14 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for t_string in value {
visitor.visit_t_string(t_string);
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),
}
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {

View File

@@ -533,8 +533,18 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for t_string in value.iter_mut() {
visitor.visit_t_string(t_string);
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);
}
}
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {

View File

@@ -51,12 +51,28 @@ fn concatenated_fstrings_compare_equal() -> Result<(), ParseError> {
#[test]
fn concatenated_tstrings_compare_equal() -> Result<(), ParseError> {
let split_contents = r#"t"{foo!r} this" rt"\n raw" t" and {bar!s} that""#;
let split_contents = r#"t"{foo!r} this" r"\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""#;
@@ -64,3 +80,11 @@ 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)
}

View File

@@ -5,8 +5,7 @@ expression: trace
- ModModule
- StmtExpr
- ExprTString
- TString
- InterpolatedStringLiteralElement
- StringLiteral
- TString
- InterpolatedStringLiteralElement
- InterpolatedElement

View File

@@ -4,8 +4,7 @@ expression: trace
---
- StmtExpr
- ExprTString
- TString
- InterpolatedStringLiteralElement
- StringLiteral
- TString
- InterpolatedStringLiteralElement
- InterpolatedElement

View File

@@ -148,7 +148,7 @@ fn f_strings() {
#[test]
fn t_strings() {
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
let source = r"'pre' t'foo {bar:.{x}f} baz'";
let trace = trace_source_order_visitation(source);

View File

@@ -157,7 +157,7 @@ fn f_strings() {
#[test]
fn t_strings() {
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
let source = r"'pre' t'foo {bar:.{x}f} baz'";
let trace = trace_visitation(source);

View File

@@ -1538,9 +1538,19 @@ impl<'a> Generator<'a> {
fn unparse_t_string_value(&mut self, value: &ast::TStringValue) {
let mut first = true;
for t_string in value {
for t_string_part in value {
self.p_delim(&mut first, " ");
self.unparse_interpolated_string(&t_string.elements, t_string.flags.into());
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());
}
}
}
}

View File

@@ -104,13 +104,16 @@ 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}" t"more"
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
# Join, but don't break the expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
t"test{
expression
@@ -168,11 +171,22 @@ t"test" tR"test"
"single" f""""single"""
t"single" t""""single"""
"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
##############################################################################

View File

@@ -345,7 +345,7 @@ a[
b
] = (
t"ccccc{
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
expression}ccccccccccc" "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" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
@@ -370,7 +370,7 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
@@ -379,66 +379,66 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
expression}cccccccccccccccccccccccccccccccc" "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,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
# Don't inline t-strings that contain commented expressions
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
=}" "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]
)

View File

@@ -8,21 +8,21 @@ rt"Not-so-tricky \"quote"
# Regression test for tstrings dropping comments
result_f = (
t'Traceback (most recent call last):\n'
'Traceback (most recent call last):\n'
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' 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
rt' \[Previous line repeated (\d+) more times\]' t'\n'
t'RecursionError: maximum recursion depth exceeded\n'
r' \[Previous line repeated (\d+) more times\]' '\n'
'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
_ = (
t'This string should change its quotes to double quotes'
'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.'
)

View File

@@ -110,13 +110,16 @@ 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}" t"more"
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
# Join, but don't break the expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
t"test{
expression
@@ -174,11 +177,22 @@ t"test" tR"test"
"single" f""""single"""
t"single" t""""single"""
"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
##############################################################################
@@ -507,6 +521,9 @@ 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
@@ -566,11 +583,22 @@ t"test" Rt"test"
"single" f""""single"""
t"single" t""""single"""
"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
##############################################################################
@@ -877,7 +905,7 @@ f"aaaaaaaaaaaaaaaa \
```diff
--- Stable
+++ Preview
@@ -288,9 +288,12 @@
@@ -302,9 +302,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.

View File

@@ -351,7 +351,7 @@ a[
b
] = (
t"ccccc{
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
expression}ccccccccccc" "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" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
@@ -376,7 +376,7 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
@@ -385,66 +385,66 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
expression}cccccccccccccccccccccccccccccccc" "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,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
# Don't inline t-strings that contain commented expressions
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
=}" "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"
t"ccccccccccccccccccccccccccccccccccccccccccc"
"ccccccccccccccccccccccccccccccccccccccccccc"
) # comment
@@ -925,7 +925,7 @@ a[
aaaaaaa, b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{expression}cccccccccccccccccccccccccccccccc"
t"ccccccccccccccccccccccccccccccc"
"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,
]
}"
t"moreeeeeeeeeeeeeeeeeeee"
t"test"
"moreeeeeeeeeeeeeeeeeeee"
"test"
) # comment
aaaaaaaaaaaaaaaaaa = (
@@ -945,8 +945,8 @@ aaaaaaaaaaaaaaaaaa = (
a,
]
}"
t"moreeeeeeeeeeeeeeeeeeee"
t"test" # comment
"moreeeeeeeeeeeeeeeeeeee"
"test" # comment
)
aaaaa[aaaaaaaaaaa] = (
@@ -955,8 +955,8 @@ aaaaa[aaaaaaaaaaa] = (
a,
]
}"
t"moreeeeeeeeeeeeeeeeeeee"
t"test"
"moreeeeeeeeeeeeeeeeeeee"
"test"
) # comment
aaaaa[aaaaaaaaaaa] = (
@@ -965,8 +965,8 @@ aaaaa[aaaaaaaaaaa] = (
a,
]
}"
t"moreeeeeeeeeeeeeeeeeeee"
t"test" # comment
"moreeeeeeeeeeeeeeeeeeee"
"test" # comment
)
# Don't inline t-strings that contain commented expressions
@@ -976,7 +976,7 @@ aaaaaaaaaaaaaaaaaa = (
a # comment
]
}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
@@ -985,38 +985,38 @@ aaaaa[aaaaaaaaaaa] = (
a # comment
]
}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}"
t"moreeeeeeeeeeeeeeeeeetest" # comment
"moreeeeeeeeeeeeeeeeeetest" # comment
)

View File

@@ -14,21 +14,21 @@ rt"Not-so-tricky \"quote"
# Regression test for tstrings dropping comments
result_f = (
t'Traceback (most recent call last):\n'
'Traceback (most recent call last):\n'
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
t' f()\n'
' 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
rt' \[Previous line repeated (\d+) more times\]' t'\n'
t'RecursionError: maximum recursion depth exceeded\n'
r' \[Previous line repeated (\d+) more times\]' '\n'
'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
_ = (
t'This string should change its quotes to double quotes'
'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 = (
t"Traceback (most recent call last):\n"
"Traceback (most recent call last):\n"
t' File "{__file__}", line {lineno_f + 5}, in _check_recursive_traceback_display\n'
t" f()\n"
" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
t" f()\n"
" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
t" f()\n"
" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
t" f()\n"
" 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
rt" \[Previous line repeated (\d+) more times\]"
t"\n"
t"RecursionError: maximum recursion depth exceeded\n"
r" \[Previous line repeated (\d+) more times\]"
"\n"
"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
_ = (
t"This string should change its quotes to double quotes"
"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."
)

View File

@@ -3,3 +3,4 @@ t"{hey}"
t'{there}'
t"""what's
happening?"""
"implicitly"t"concatenated"

View File

@@ -3,3 +3,4 @@ t"{hey}"
t'{there}'
t"""what's
happening?"""
"implicitly"t"concatenated"

View File

@@ -17,7 +17,7 @@ t"{ foo = !s }"
t"{ 1, 2 = }"
t'{t"{3.1415=:.1f}":*^20}'
{t"foo " t"bar {x + y} " t"baz": 10}
{"foo " t"bar {x + y} " "baz": 10}
match foo:
case "one":
pass
@@ -44,18 +44,31 @@ t"{x=!a}"
t"{x:.3f!r =}"
t"{x = !r :.3f}"
t"{x:.3f=!r}"
t"hello" t"{x}"
"hello" t"{x}"
t"{x}" t"{y}"
t"{x}" t"world"
t"{x}" "world"
t"Invalid args in command: {command, *args}"
t"foo" t"{x}" t"bar"
"foo" t"{x}" "bar"
(
t"a"
t"b"
t"c"
"c"
rt"d"
tr"e"
fr"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}"}"}"

View File

@@ -1,3 +1,4 @@
use std::cmp::Ordering;
use std::ops::Deref;
use bitflags::bitflags;
@@ -1255,6 +1256,7 @@ impl<'src> Parser<'src> {
// t'{there}'
// t"""what's
// happening?"""
// "implicitly"t"concatenated"
// test_err template_strings_py313
// # parse_options: {"target-version": "3.13"}
@@ -1262,6 +1264,7 @@ 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(),
@@ -1278,7 +1281,7 @@ impl<'src> Parser<'src> {
match strings.len() {
// This is not possible as the function was called by matching against a
// `String`, `FStringStart`, or `TStringStart` token.
// `String` or `FStringStart` token.
0 => unreachable!("Expected to parse at least one string"),
// We need a owned value, hence the `pop` here.
1 => match strings.pop().unwrap() {
@@ -1319,84 +1322,58 @@ 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(_) => tstring_count += 1,
StringType::TString(_) => has_tstring = true,
StringType::Bytes(_) => byte_literal_count += 1,
StringType::Str(_) => {}
}
}
let has_bytes = byte_literal_count > 0;
let has_tstring = tstring_count > 0;
if has_bytes {
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.
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.
// 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`"),
// 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(),
});
}
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(),
});
Ordering::Greater => unreachable!(),
}
}
@@ -1437,17 +1414,36 @@ 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()),
)),

View File

@@ -13,16 +13,18 @@ expression: suite
range: 0..3,
value: TStringValue {
inner: Single(
TString {
range: 0..3,
node_index: AtomicNodeIndex(..),
elements: [],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..3,
node_index: AtomicNodeIndex(..),
elements: [],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -0,0 +1,64 @@
---
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,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,76 @@
---
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,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,56 @@
---
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,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,56 @@
---
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,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,85 @@
---
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(
[
Literal(
StringLiteral {
range: 0..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,97 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..31,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..31,
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..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 23..31,
node_index: AtomicNodeIndex(..),
value: "again!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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..31,
}

View File

@@ -13,58 +13,60 @@ expression: suite
range: 0..18,
value: TStringValue {
inner: Single(
TString {
range: 0..18,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("a"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..8,
id: Name("b"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..17,
node_index: AtomicNodeIndex(..),
value: "{foo}",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..18,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("a"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..8,
id: Name("b"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..17,
node_index: AtomicNodeIndex(..),
value: "{foo}",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,55 +13,57 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..11,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..5,
value: Int(
42,
),
},
),
ops: [
Eq,
],
comparators: [
NumberLiteral(
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..11,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 9..11,
range: 3..5,
value: Int(
42,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
ops: [
Eq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 9..11,
value: Int(
42,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,85 +13,87 @@ expression: suite
range: 0..16,
value: TStringValue {
inner: Single(
TString {
range: 0..16,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..15,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..14,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
TString(
TString {
range: 0..16,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..15,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..14,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
},
StringLiteral {
range: 11..13,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
StringLiteral {
range: 11..13,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
},
],
value: "",
},
),
],
value: "",
},
),
},
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,58 +13,60 @@ expression: suite
range: 0..15,
value: TStringValue {
inner: Single(
TString {
range: 0..15,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..14,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 8..12,
id: Name("spec"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..15,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..14,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 8..12,
id: Name("spec"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,70 +13,72 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
},
),
),
},
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,55 +13,57 @@ expression: suite
range: 0..11,
value: TStringValue {
inner: Single(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..10,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..9,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..4,
value: Int(
1,
),
},
),
ops: [
NotEq,
],
comparators: [
NumberLiteral(
TString(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..10,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..9,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 8..9,
range: 3..4,
value: Int(
2,
1,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
ops: [
NotEq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 8..9,
value: Int(
2,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,48 +13,50 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
value: "spec",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
value: "spec",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,39 +13,41 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,39 +13,41 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -13,33 +13,35 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Yield(
ExprYield {
node_index: AtomicNodeIndex(..),
range: 3..8,
value: None,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Yield(
ExprYield {
node_index: AtomicNodeIndex(..),
range: 3..8,
value: None,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
},
),
),
},
},

View File

@@ -0,0 +1,56 @@
---
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(
[
Literal(
StringLiteral {
range: 0..9,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
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,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -1,10 +0,0 @@
---
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,
}

View File

@@ -0,0 +1,68 @@
---
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(
[
Literal(
StringLiteral {
range: 0..9,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
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,
},
},
),
],
),
},
},
),
},
),
]

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