Skip to content

Commit 2e1d662

Browse files
authored
[flake8-simplify] Implement fix for maxsplit without separator (SIM905) (#19851)
**Stacked on top of #19849; diff will include that PR until it is merged.** --- <!-- 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 part of #19849, I noticed this fix could be implemented. ## Test Plan Tests added based on CPython behaviour.
1 parent 2dc2f68 commit 2e1d662

File tree

6 files changed

+1650
-6
lines changed

6 files changed

+1650
-6
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,7 @@
166166
print("S\x1cP\x1dL\x1eI\x1fT".split())
167167
print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
168168
print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
169+
170+
# leading/trailing whitespace should not count towards maxsplit
171+
" a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
172+
" a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]

crates/ruff_linter/src/preview.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,8 @@ pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterS
230230
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
231231
settings.preview.is_enabled()
232232
}
233+
234+
// https://github.com/astral-sh/ruff/pull/19851
235+
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
236+
settings.preview.is_enabled()
237+
}

crates/ruff_linter/src/rules/flake8_simplify/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod tests {
6060
}
6161

6262
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
63+
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
6364
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
6465
let snapshot = format!(
6566
"preview__{}_{}",

crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use ruff_python_ast::{
99
use ruff_text_size::{Ranged, TextRange};
1010

1111
use crate::checkers::ast::Checker;
12+
use crate::preview::is_maxsplit_without_separator_fix_enabled;
13+
use crate::settings::LinterSettings;
1214
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
1315

1416
/// ## What it does
@@ -84,7 +86,9 @@ pub(crate) fn split_static_string(
8486
let sep_arg = arguments.find_argument_value("sep", 0);
8587
let split_replacement = if let Some(sep) = sep_arg {
8688
match sep {
87-
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction),
89+
Expr::NoneLiteral(_) => {
90+
split_default(str_value, maxsplit_value, direction, checker.settings())
91+
}
8892
Expr::StringLiteral(sep_value) => {
8993
let sep_value_str = sep_value.value.to_str();
9094
Some(split_sep(
@@ -100,7 +104,7 @@ pub(crate) fn split_static_string(
100104
}
101105
}
102106
} else {
103-
split_default(str_value, maxsplit_value, direction)
107+
split_default(str_value, maxsplit_value, direction, checker.settings())
104108
};
105109

106110
let mut diagnostic = checker.report_diagnostic(SplitStaticString, call.range());
@@ -174,6 +178,7 @@ fn split_default(
174178
str_value: &StringLiteralValue,
175179
max_split: i32,
176180
direction: Direction,
181+
settings: &LinterSettings,
177182
) -> Option<Expr> {
178183
// From the Python documentation:
179184
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
@@ -185,10 +190,31 @@ fn split_default(
185190
let string_val = str_value.to_str();
186191
match max_split.cmp(&0) {
187192
Ordering::Greater => {
188-
// Autofix for `maxsplit` without separator not yet implemented, as
189-
// `split_whitespace().remainder()` is not stable:
190-
// https://doc.rust-lang.org/std/str/struct.SplitWhitespace.html#method.remainder
191-
None
193+
if !is_maxsplit_without_separator_fix_enabled(settings) {
194+
return None;
195+
}
196+
let Ok(max_split) = usize::try_from(max_split) else {
197+
return None;
198+
};
199+
let list_items: Vec<&str> = if direction == Direction::Left {
200+
string_val
201+
.trim_start_matches(py_unicode_is_whitespace)
202+
.splitn(max_split + 1, py_unicode_is_whitespace)
203+
.filter(|s| !s.is_empty())
204+
.collect()
205+
} else {
206+
let mut items: Vec<&str> = string_val
207+
.trim_end_matches(py_unicode_is_whitespace)
208+
.rsplitn(max_split + 1, py_unicode_is_whitespace)
209+
.filter(|s| !s.is_empty())
210+
.collect();
211+
items.reverse();
212+
items
213+
};
214+
Some(construct_replacement(
215+
&list_items,
216+
str_value.first_literal_flags(),
217+
))
192218
}
193219
Ordering::Equal => {
194220
// Behavior for maxsplit = 0 when sep is None:

crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,7 @@ help: Replace with list literal
14391439
166 |+print(["S", "P", "L", "I", "T"])
14401440
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
14411441
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
1442+
169 169 |
14421443

14431444
SIM905 [*] Consider using a list literal instead of `str.split`
14441445
--> SIM905.py:167:7
@@ -1458,6 +1459,8 @@ help: Replace with list literal
14581459
167 |-print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
14591460
167 |+print([">"])
14601461
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
1462+
169 169 |
1463+
170 170 | # leading/trailing whitespace should not count towards maxsplit
14611464

14621465
SIM905 [*] Consider using a list literal instead of `str.split`
14631466
--> SIM905.py:168:7
@@ -1466,6 +1469,8 @@ SIM905 [*] Consider using a list literal instead of `str.split`
14661469
167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
14671470
168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
14681471
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1472+
169 |
1473+
170 | # leading/trailing whitespace should not count towards maxsplit
14691474
|
14701475
help: Replace with list literal
14711476

@@ -1475,3 +1480,26 @@ help: Replace with list literal
14751480
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
14761481
168 |-print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
14771482
168 |+print(["<"])
1483+
169 169 |
1484+
170 170 | # leading/trailing whitespace should not count towards maxsplit
1485+
171 171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
1486+
1487+
SIM905 Consider using a list literal instead of `str.split`
1488+
--> SIM905.py:171:1
1489+
|
1490+
170 | # leading/trailing whitespace should not count towards maxsplit
1491+
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
1492+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1493+
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
1494+
|
1495+
help: Replace with list literal
1496+
1497+
SIM905 Consider using a list literal instead of `str.split`
1498+
--> SIM905.py:172:1
1499+
|
1500+
170 | # leading/trailing whitespace should not count towards maxsplit
1501+
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
1502+
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
1503+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1504+
|
1505+
help: Replace with list literal

0 commit comments

Comments
 (0)