mirror of https://github.com/astral-sh/ruff
[`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.
This commit is contained in:
parent
2dc2f68b0f
commit
2e1d6623cd
|
|
@ -166,3 +166,7 @@ r"""first
|
|||
print("S\x1cP\x1dL\x1eI\x1fT".split())
|
||||
print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
|
||||
print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
|
||||
# leading/trailing whitespace should not count towards maxsplit
|
||||
" a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
" a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
|
|
|
|||
|
|
@ -230,3 +230,8 @@ pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterS
|
|||
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19851
|
||||
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
|
||||
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use ruff_python_ast::{
|
|||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_maxsplit_without_separator_fix_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
|
|
@ -84,7 +86,9 @@ pub(crate) fn split_static_string(
|
|||
let sep_arg = arguments.find_argument_value("sep", 0);
|
||||
let split_replacement = if let Some(sep) = sep_arg {
|
||||
match sep {
|
||||
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction),
|
||||
Expr::NoneLiteral(_) => {
|
||||
split_default(str_value, maxsplit_value, direction, checker.settings())
|
||||
}
|
||||
Expr::StringLiteral(sep_value) => {
|
||||
let sep_value_str = sep_value.value.to_str();
|
||||
Some(split_sep(
|
||||
|
|
@ -100,7 +104,7 @@ pub(crate) fn split_static_string(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
split_default(str_value, maxsplit_value, direction)
|
||||
split_default(str_value, maxsplit_value, direction, checker.settings())
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(SplitStaticString, call.range());
|
||||
|
|
@ -174,6 +178,7 @@ fn split_default(
|
|||
str_value: &StringLiteralValue,
|
||||
max_split: i32,
|
||||
direction: Direction,
|
||||
settings: &LinterSettings,
|
||||
) -> Option<Expr> {
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
|
|
@ -185,10 +190,31 @@ fn split_default(
|
|||
let string_val = str_value.to_str();
|
||||
match max_split.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
// Autofix for `maxsplit` without separator not yet implemented, as
|
||||
// `split_whitespace().remainder()` is not stable:
|
||||
// https://doc.rust-lang.org/std/str/struct.SplitWhitespace.html#method.remainder
|
||||
None
|
||||
if !is_maxsplit_without_separator_fix_enabled(settings) {
|
||||
return None;
|
||||
}
|
||||
let Ok(max_split) = usize::try_from(max_split) else {
|
||||
return None;
|
||||
};
|
||||
let list_items: Vec<&str> = if direction == Direction::Left {
|
||||
string_val
|
||||
.trim_start_matches(py_unicode_is_whitespace)
|
||||
.splitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
let mut items: Vec<&str> = string_val
|
||||
.trim_end_matches(py_unicode_is_whitespace)
|
||||
.rsplitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
items.reverse();
|
||||
items
|
||||
};
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Behavior for maxsplit = 0 when sep is None:
|
||||
|
|
|
|||
|
|
@ -1439,6 +1439,7 @@ help: Replace with list literal
|
|||
166 |+print(["S", "P", "L", "I", "T"])
|
||||
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
|
||||
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
169 169 |
|
||||
|
||||
SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:167:7
|
||||
|
|
@ -1458,6 +1459,8 @@ help: Replace with list literal
|
|||
167 |-print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
|
||||
167 |+print([">"])
|
||||
168 168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
169 169 |
|
||||
170 170 | # leading/trailing whitespace should not count towards maxsplit
|
||||
|
||||
SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:168:7
|
||||
|
|
@ -1466,6 +1469,8 @@ SIM905 [*] Consider using a list literal instead of `str.split`
|
|||
167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
|
||||
168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
169 |
|
||||
170 | # leading/trailing whitespace should not count towards maxsplit
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
||||
|
|
@ -1475,3 +1480,26 @@ help: Replace with list literal
|
|||
167 167 | print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
|
||||
168 |-print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
168 |+print(["<"])
|
||||
169 169 |
|
||||
170 170 | # leading/trailing whitespace should not count towards maxsplit
|
||||
171 171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
|
||||
SIM905 Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:171:1
|
||||
|
|
||||
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"]
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
||||
SIM905 Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:172:1
|
||||
|
|
||||
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"]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue