[ruff] Detect PLC0207 on chained str.split() calls#23275
[ruff] Detect PLC0207 on chained str.split() calls#23275ntBre merged 1 commit intoastral-sh:mainfrom
ruff] Detect PLC0207 on chained str.split() calls#23275Conversation
Fixes astral-sh#23105 The `is_string` check in PLC0207 failed to recognize that `str.split()` returns `list[str]`, so subscripting it yields a `str`. This caused the rule to miss chained splits like `s.split('(')[0].split('[')[0]`. Extend the target-instance unwrapping loop to also recurse through `split`/`rsplit` Call expressions, reaching the original string value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
str.split() callsPLC0207 on chained str.split() calls
PLC0207 on chained str.split() callsruff] Detect PLC0207 on chained str.split() calls
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| PLC0207 | 7 | 7 | 0 | 0 | 0 |
Linter (preview)
ℹ️ ecosystem check detected linter changes. (+7 -0 violations, +0 -0 fixes in 4 projects; 52 projects unchanged)
apache/airflow (+1 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL
+ providers/google/src/airflow/providers/google/cloud/utils/credentials_provider.py:484:16: PLC0207 [*] String is split more times than necessary
apache/superset (+3 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL
+ superset/db_engine_specs/lint_metadata.py:100:17: PLC0207 [*] String is split more times than necessary + superset/db_engine_specs/lint_metadata.py:100:17: PLC0207 [*] String is split more times than necessary + superset/db_engine_specs/lint_metadata.py:100:17: PLC0207 [*] String is split more times than necessary
pypa/cibuildwheel (+1 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview
+ cibuildwheel/selector.py:78:26: PLC0207 [*] String is split more times than necessary
home-assistant/core (+2 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview
+ homeassistant/components/bang_olufsen/util.py:37:12: PLC0207 [*] String is split more times than necessary + homeassistant/components/roomba/config_flow.py:343:12: PLC0207 [*] String is split more times than necessary
Changes by rule (1 rules affected)
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| PLC0207 | 7 | 7 | 0 | 0 | 0 |
ntBre
left a comment
There was a problem hiding this comment.
Thank you!
It feels slightly silly to do this since it's just a special case of inferring that the target of the split call is a string, but I'm okay with special casing it since it comes up several times in the ecosystem, and it's also a very obvious false negative since the first part of the same expression gets a diagnostic.
I also double-checked that the fixes apply cleanly in one pass:
❯ just run check --select PLC0207 --fix - <<'EOF'
SEQ = ""
SEQ.split("(")[0].split("[")[0].split(".")[-1]
EOF
cargo run -p ruff -- check --select PLC0207 --fix -
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/ruff check --select PLC0207 --fix -`
warning: Detected debug build without --no-cache.
SEQ = ""
SEQ.split("(", maxsplit=1)[0].split("[", maxsplit=1)[0].rsplit(".", maxsplit=1)[-1]
Found 3 errors (3 fixed, 0 remaining).I'm always slightly wary about overlapping fixes since I don't think our fixtures exercise that, but this works well!
Summary
Fixes #23105
Extend the target-instance unwrapping loop to also recurse through
split/rsplitCall expressions so that chained splits likes.split('(')[0].split('[')[0]are detected.Test plan
Added test cases for chained splits.
🤖 Generated with Claude Code