Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions crates/ruff_linter/src/rules/pylint/rules/missing_maxsplit_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use crate::Violation;
use crate::checkers::ast::Checker;

/// ## What it does
/// Checks for access to the first or last element of `str.split()` without
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
/// `maxsplit=1`
///
/// ## Why is this bad?
/// Calling `str.split()` without `maxsplit` set splits on every delimiter in the
/// Calling `str.split()` or `str.rsplit()` without passing `maxsplit=1` splits on every delimiter in the
/// string. When accessing only the first or last element of the result, it
/// would be more efficient to only split once.
///
Expand All @@ -29,14 +29,44 @@ use crate::checkers::ast::Checker;
/// url = "www.example.com"
/// prefix = url.split(".", maxsplit=1)[0]
/// ```
///
/// To access the last element, use `str.rsplit()` instead of `str.split()`:
/// ```python
/// url = "www.example.com"
/// suffix = url.rsplit(".", maxsplit=1)[-1]
/// ```
#[derive(ViolationMetadata)]
pub(crate) struct MissingMaxsplitArg;
pub(crate) struct MissingMaxsplitArg {
index: SliceBoundary,
actual_split_type: String,
}

/// Represents the index of the slice used for this rule (which can only be 0 or -1)
enum SliceBoundary {
First,
Last,
}

impl Violation for MissingMaxsplitArg {
#[derive_message_formats]
fn message(&self) -> String {
"Accessing only the first or last element of `str.split()` without setting `maxsplit=1`"
.to_string()
let MissingMaxsplitArg {
index,
actual_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!(
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
)
}
}
}

Expand Down Expand Up @@ -82,9 +112,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
_ => return,
};

if !matches!(index, Some(0 | -1)) {
return;
}
let slice_boundary = match index {
Some(0) => SliceBoundary::First,
Some(-1) => SliceBoundary::Last,
_ => return,
};

let Expr::Attribute(ExprAttribute { attr, value, .. }) = func.as_ref() else {
return;
Expand Down Expand Up @@ -129,5 +161,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
}
}

checker.report_diagnostic(MissingMaxsplitArg, expr.range());
checker.report_diagnostic(
MissingMaxsplitArg {
index: slice_boundary,
actual_split_type: attr.to_string(),
},
expr.range(),
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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
Expand All @@ -11,7 +11,7 @@ missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element o
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -21,7 +21,7 @@ missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element o
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -30,7 +30,7 @@ missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element o
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -40,7 +40,7 @@ missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element o
19 | ## Test split called on string variable
|

missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -49,7 +49,7 @@ missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element o
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -59,7 +59,7 @@ missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element o
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -68,7 +68,7 @@ missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element o
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -78,7 +78,7 @@ missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element o
25 | ## Test split called on class attribute
|

missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -87,7 +87,7 @@ missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element o
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -97,7 +97,7 @@ missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element o
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -106,7 +106,7 @@ missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element o
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -116,7 +116,7 @@ missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element o
31 | ## Test split called on sliced string
|

missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -125,7 +125,7 @@ missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element o
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -135,7 +135,7 @@ missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element o
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -145,7 +145,7 @@ missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element o
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -155,7 +155,7 @@ missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element o
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -165,7 +165,7 @@ missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element o
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -174,7 +174,7 @@ missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element o
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -184,7 +184,7 @@ missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element o
40 | ## Test sep given as named argument
|

missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -193,7 +193,7 @@ missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element o
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -203,7 +203,7 @@ missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element o
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -212,7 +212,7 @@ missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element o
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -222,7 +222,7 @@ missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element o
46 | ## Special cases
|

missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -231,7 +231,7 @@ missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element o
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -240,7 +240,7 @@ missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element o
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -250,7 +250,7 @@ missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element o
51 | ## Test class attribute named split
|

missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -259,7 +259,7 @@ missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element o
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -269,7 +269,7 @@ missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element o
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -278,7 +278,7 @@ missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element o
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|

missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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]
Expand All @@ -288,14 +288,14 @@ missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element o
57 | ## Test unpacked dict literal kwargs
|

missing_maxsplit_arg.py:58:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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
|

missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
177 | # Errors
178 | kwargs_without_maxsplit = {"seq": ","}
Expand All @@ -305,7 +305,7 @@ missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|

missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
Expand All @@ -315,7 +315,7 @@ missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|

missing_maxsplit_arg.py:184:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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}
Expand Down
Loading