Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
40 changes: 34 additions & 6 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,36 @@ 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: i64,
function: String,
}

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, function } = self;

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.

};

if function == correct_function {
format!("Pass `maxsplit=1` into `str.{function}()`")
} else {
format!(
"Instead of `str.{function}()`, call `str.{correct_function}()` and pass `maxsplit=1`",
)
}
}
}

Expand Down Expand Up @@ -129,5 +151,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
}
}

checker.report_diagnostic(MissingMaxsplitArg, expr.range());
checker.report_diagnostic(
MissingMaxsplitArg {
index: index.expect("Invalid slice index"),
function: 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