Skip to content

Conversation

@junhsonjb
Copy link
Contributor

Summary

Fix #18383 by updating the documentation and error message to explain that users should use rsplit in order to access the last element of the result with maxsplit=1

Test Plan

Only documentation and an error message was changed. As such, snapshots were updated to reflect the new error message. With this change, all existing tests pass.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 26, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+46 -46 violations, +0 -0 fixes in 13 projects; 42 projects unchanged)

PlasmaPy/PlasmaPy (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- noxfile.py:453:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ noxfile.py:453:16: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`

apache/airflow (+9 -9 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- dev/backport/update_backport_status.py:78:21: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ dev/backport/update_backport_status.py:78:21: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- dev/breeze/src/airflow_breeze/commands/common_options.py:482:26: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ dev/breeze/src/airflow_breeze/commands/common_options.py:482:26: PLC0207 Pass `maxsplit=1` into `str.split()`
- dev/breeze/src/airflow_breeze/commands/release_management_commands.py:2631:50: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ dev/breeze/src/airflow_breeze/commands/release_management_commands.py:2631:50: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py:701:34: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py:701:34: PLC0207 Pass `maxsplit=1` into `str.split()`
- dev/breeze/src/airflow_breeze/utils/run_utils.py:120:25: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ dev/breeze/src/airflow_breeze/utils/run_utils.py:120:25: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
... 8 additional changes omitted for project

apache/superset (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- tests/common/query_context_generator.py:214:29: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ tests/common/query_context_generator.py:214:29: PLC0207 Pass `maxsplit=1` into `str.split()`
- tests/common/query_context_generator.py:255:22: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ tests/common/query_context_generator.py:255:22: PLC0207 Pass `maxsplit=1` into `str.split()`

bokeh/bokeh (+3 -3 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- src/bokeh/io/notebook.py:663:26: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/bokeh/io/notebook.py:663:26: PLC0207 Pass `maxsplit=1` into `str.split()`
- src/bokeh/util/token.py:142:41: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/bokeh/util/token.py:142:41: PLC0207 Pass `maxsplit=1` into `str.split()`
- src/bokeh/util/token.py:155:41: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/bokeh/util/token.py:155:41: PLC0207 Pass `maxsplit=1` into `str.split()`

freedomofpress/securedrop (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- securedrop/store.py:345:49: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ securedrop/store.py:345:49: PLC0207 Pass `maxsplit=1` into `str.split()`

ibis-project/ibis (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- ibis/examples/gen_registry.py:125:52: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ ibis/examples/gen_registry.py:125:52: PLC0207 Pass `maxsplit=1` into `str.split()`

langchain-ai/langchain (+9 -9 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- libs/core/langchain_core/_api/deprecation.py:360:17: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ libs/core/langchain_core/_api/deprecation.py:360:17: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- libs/core/langchain_core/_api/deprecation.py:360:56: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ libs/core/langchain_core/_api/deprecation.py:360:56: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- libs/core/langchain_core/_api/deprecation.py:368:17: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ libs/core/langchain_core/_api/deprecation.py:368:17: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- libs/core/langchain_core/_api/deprecation.py:369:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ libs/core/langchain_core/_api/deprecation.py:369:16: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- libs/core/langchain_core/_api/deprecation.py:473:24: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ libs/core/langchain_core/_api/deprecation.py:473:24: PLC0207 Pass `maxsplit=1` into `str.split()`
... 8 additional changes omitted for project

pandas-dev/pandas (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- pandas/compat/_optional.py:166:14: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ pandas/compat/_optional.py:166:14: PLC0207 Pass `maxsplit=1` into `str.split()`

pypa/cibuildwheel (+3 -3 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- bin/update_pythons.py:150:22: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ bin/update_pythons.py:150:22: PLC0207 Pass `maxsplit=1` into `str.split()`
- cibuildwheel/platforms/macos.py:153:31: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ cibuildwheel/platforms/macos.py:153:31: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- cibuildwheel/selector.py:79:26: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ cibuildwheel/selector.py:79:26: PLC0207 Pass `maxsplit=1` into `str.split()`

zulip/zulip (+11 -11 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL

- zerver/lib/send_email.py:287:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ zerver/lib/send_email.py:287:16: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- zerver/lib/send_email.py:429:21: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ zerver/lib/send_email.py:429:21: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- zerver/lib/upload/local.py:67:17: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ zerver/lib/upload/local.py:67:17: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- zerver/lib/upload/s3.py:176:25: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ zerver/lib/upload/s3.py:176:25: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
- zerver/lib/webhooks/common.py:242:26: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ zerver/lib/webhooks/common.py:242:26: PLC0207 Pass `maxsplit=1` into `str.split()`
- zerver/lib/webhooks/common.py:269:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
... 11 additional changes omitted for project

wntrblm/nox (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- tests/test_sessions.py:1199:44: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ tests/test_sessions.py:1199:44: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`

pytest-dev/pytest (+3 -3 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- src/_pytest/config/findpaths.py:160:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/_pytest/config/findpaths.py:160:16: PLC0207 Pass `maxsplit=1` into `str.split()`
- src/_pytest/terminal.py:1012:40: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/_pytest/terminal.py:1012:40: PLC0207 Pass `maxsplit=1` into `str.split()`
- src/_pytest/terminal.py:462:41: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ src/_pytest/terminal.py:462:41: PLC0207 Pass `maxsplit=1` into `str.split()`

astropy/astropy (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- astropy/io/ascii/latex.py:438:16: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
+ astropy/io/ascii/latex.py:438:16: PLC0207 Pass `maxsplit=1` into `str.split()`

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
PLC0207 92 46 46 0 0

@ntBre ntBre self-requested a review June 26, 2025 01:56
@ntBre ntBre added the documentation Improvements or additions to documentation label Jun 26, 2025
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this really addresses the problem in the issue. The confusion isn't from the rule applying to either split or rsplit calls, it's the fact that the fix might change which call you need.

This is the confusing case from the issue:

url = "www.example.com"
url.split(".")[-1]  # result is "com"

And the diagnostic from Ruff:

-:2:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
  |
1 | url = "www.example.com"
2 | url.split(".")[-1]  # result is "com"
  | ^^^^^^^^^^^^^^^^^^ PLC0207
  |

The error message implies that the fix is simply to add maxsplit=1, but that gives a different result:

```python
url = "www.example.com"
url.split(".", maxsplit=1)[-1]  # result is now "example.com" instead of "com"

The correct fix is to add maxplit=1 and change split to rsplit, restoring the original behavior.

That's the kind of information we need to include in the rule documentation and in the error message. In the latter case, we'll need to inspect the slice ([0] or [-1]) and call type (split or rsplit) and customize the error message accordingly so that we actually suggest the right thing for the specific situation.

@junhsonjb
Copy link
Contributor Author

Thanks for the comments, that makes sense! I just patched some updates to address them!

@junhsonjb junhsonjb requested a review from ntBre June 30, 2025 23:27
let correct_function = match index {
0 => "split",
-1 => "rsplit",
_ => "", // We should never hit this case
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an unreachable! instead, since if that case is hit it should cause a failure instead of a silent issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably define a simple enum instead of using an i64. That way we can check for known values up front and bail out of the whole diagnostic. Then we can match exhaustively here.

@junhsonjb junhsonjb requested a review from MeGaGiGaGon July 1, 2025 14:07
@junhsonjb junhsonjb changed the title Update missing-maxsplit-arg docs and error to explain rsplit usage Update missing-maxsplit-arg docs and error to suggest proper split/rsplit usage along with maxsplit=1 Jul 2, 2025
@junhsonjb junhsonjb changed the title Update missing-maxsplit-arg docs and error to suggest proper split/rsplit usage along with maxsplit=1 Update missing-maxsplit-arg docs and error to suggest proper usage Jul 2, 2025
@junhsonjb
Copy link
Contributor Author

Hey @ntBre — just wanted to check in on this. I’ve pushed some updates addressing your feedback above. When you get the chance, let me know if there’s anything else you’d like me to adjust. Thanks again! 🙂

Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, thank you! And thanks for the ping.

I'm pretty sure these changes give us all the information we need to generate an autofix too. Feel free to work on that in a follow-up PR if you're interested :)

@ntBre ntBre changed the title Update missing-maxsplit-arg docs and error to suggest proper usage [pylint] Update missing-maxsplit-arg docs and error to suggest proper usage (PLC0207) Jul 8, 2025
@ntBre ntBre merged commit fda1889 into astral-sh:main Jul 8, 2025
35 checks passed
UnboundVariable pushed a commit to UnboundVariable/ruff that referenced this pull request Jul 10, 2025
…re_help

* 'main' of https://github.com/astral-sh/ruff: (34 commits)
  [docs] add capital one to who's using ruff (astral-sh#19248)
  [`pyupgrade`] Keyword arguments in `super` should suppress the `UP008` fix (astral-sh#19131)
  [`flake8-use-pathlib`] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` (astral-sh#19213)
  [ty] Do not run `mypy_primer.yaml` when all changed files are Markdown files (astral-sh#19244)
  [`flake8-bandit`] Make example error out-of-the-box (`S412`) (astral-sh#19241)
  [`pydoclint`] Make example error out-of-the-box (`DOC501`) (astral-sh#19218)
  [ty] Add "kind" to completion suggestions
  [ty] Add type information to `all_members` API
  [ty] Expand API of `all_members` to return a struct
  [ty] Ecosystem analyzer PR comment workflow (astral-sh#19237)
  [ty] Merge `ty_macros` into `ruff_macros` (astral-sh#19229)
  [ty] Fix `ClassLiteral.into_callable` for dataclasses (astral-sh#19192)
  [ty] `dataclasses.field` support (astral-sh#19140)
  [ty] Fix panic for attribute expressions with empty value (astral-sh#19069)
  [ty] Return `CallableType` from `BoundMethodType.into_callable_type` (astral-sh#19193)
  [`flake8-bugbear`] Support non-context-manager calls in `B017` (astral-sh#19063)
  [ty] Improved diagnostic for reassignments of `Final` symbols (astral-sh#19214)
  [ty] Use full range for assignment definitions (astral-sh#19211)
  [`pylint`] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) (astral-sh#18949)
  [ty] Add `set -eu` to mypy-primer script (astral-sh#19212)
  ...

# Conflicts:
#	crates/ty_python_semantic/src/types/class.rs
@junhsonjb
Copy link
Contributor Author

This looks great, thank you! And thanks for the ping.

I'm pretty sure these changes give us all the information we need to generate an autofix too. Feel free to work on that in a follow-up PR if you're interested :)

@ntBre Great idea! PR is here

ntBre pushed a commit that referenced this pull request Jul 28, 2025
…19387)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary
As a follow-up to #18949 (suggested
[here](#18949 (review))),
this PR implements auto-fix logic for `PLC0207`.

## Test Plan

<!-- How was it tested? -->

Existing tests pass, with updates to the snapshot so that it expects the
new output that comes along with the auto-fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

missing-maxsplit-arg (PLC0207) is wrong when accessing on the last value

3 participants