Skip to content

changelog: new lint: [split_with_space] #13059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5826,6 +5826,7 @@ Released 2018-09-13
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`split_with_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#split_with_space
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO,
crate::string_patterns::SINGLE_CHAR_PATTERN_INFO,
crate::strings::SPLIT_WITH_SPACE_INFO,
crate::strings::STRING_ADD_INFO,
crate::strings::STRING_ADD_ASSIGN_INFO,
crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(pub_use::PubUse));
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
store.register_late_pass(|_| Box::new(strings::SplitWithSpace));
store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace));
store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
store.register_early_pass(|| Box::<duplicate_mod::DuplicateMod>::default());
Expand Down
44 changes: 44 additions & 0 deletions clippy_lints/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clippy_utils::{
get_expr_use_or_unification_node, get_parent_expr, is_lint_allowed, is_path_diagnostic_item, method_calls,
peel_blocks, SpanlessEq,
};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, Node, QPath};
Expand Down Expand Up @@ -467,6 +468,49 @@ impl<'tcx> LateLintPass<'tcx> for StringToString {
}
}

declare_clippy_lint! {
/// ### What it does
/// Checks for calling `str::split` with the ASCII space character instead of `str::split_whitespace`.
///
/// ### Why is this bad?
/// `split` using the ASCII space character does not handle cases where there is a different space character used. `split_whitespace` handles all whitespace characters.
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to mention that split_whitespace also handles multiple consecutive spaces

///
/// ### Example
/// ```no_run
/// "A B C".split(' ');
/// ```
/// Use instead:
/// ```no_run
/// "A B C".split_whitespace();
/// ```
#[clippy::version = "1.81.0"]
pub SPLIT_WITH_SPACE,
pedantic,
"using `str::split` with the ASCII space character instead of `str::split_whitespace`"
}
declare_lint_pass!(SplitWithSpace => [SPLIT_WITH_SPACE]);

impl<'tcx> LateLintPass<'tcx> for SplitWithSpace {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if let ExprKind::MethodCall(path, _split_recv, [arg], split_ws_span) = expr.kind
Copy link
Member

Choose a reason for hiding this comment

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

This should check that the receiver is really a &str and not some other type with a split method that may not have split_whitespace (see this)

&& path.ident.name == sym!(split)
&& let ExprKind::Lit(lit) = arg.kind
&& (matches!(lit.node, LitKind::Char(' '))
|| matches!(lit.node, LitKind::Str(str, _) if str.as_str() == " "))
Copy link
Member

Choose a reason for hiding this comment

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

There should be a check here that this isn't from a macro, (or at least, not from an external macro since the user can't change that, can use in_external_macro for this)

{
span_lint_and_sugg(
cx,
SPLIT_WITH_SPACE,
split_ws_span.with_hi(split_ws_span.lo()),
"found call to `str::split` that uses the ASCII space character as an argument".to_string(),
"replace the use of `str::split` with `str::split_whitespace`".to_string(),
String::new(),
Copy link
Member

Choose a reason for hiding this comment

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

The current span & suggestion seems to replace nothing?

I think it would be good to use multipart_suggestion and replace arg.span with "", and split_ws_span with "split_whitespace", which would generate a diagnostic like

help: replace the use of `str::split` with `str::split_whitespace`
   |
LL - some_space_delimtetered_string.split(" ")
LL + some_space_delimtetered_string.split_whitespace()

Applicability::MachineApplicable,
Copy link
Member

Choose a reason for hiding this comment

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

...It should probably not be MachineApplicable though.

Although in the majority of cases I imagine this is strictly more correct, there might be weird edge cases where people do want to split by exactly that, and rely on "a b".split(' ').count() == 3, which fails with split_whitespace, and MachineApplicable should not change behavior that the user needs

);
}
}
}

declare_clippy_lint! {
/// ### What it does
/// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/split_with_space.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//@aux-build:proc_macros.rs
Copy link
Member

Choose a reason for hiding this comment

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

This test links proc_macros.rs but never actually uses it.

It probably should, though, so this is a good idea. Can you add a test with proc macros? You can look at e.g. this file to see how to write a test that uses this.
You'll also likely need to add a !is_from_proc_macro() check to the lint code.

//@no-rustfix

#[deny(clippy::split_with_space)]
#[allow(unused)]
Copy link
Member

Choose a reason for hiding this comment

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

I think allow(unused) is already implicitly passed, so this probably isn't needed

fn main() {
let some_space_delimtetered_string = "Hello everyone! How are you doing?";

for substr in some_space_delimtetered_string.split(' ') {
println!("{substr}");
}

for substr in some_space_delimtetered_string.split(" ") {
println!("{substr}");
}
Copy link
Member

Choose a reason for hiding this comment

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

There should be some negative tests, e.g. splitting by something that isn't a space.

}
20 changes: 20 additions & 0 deletions tests/ui/split_with_space.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: found call to `str::split` that uses the ASCII space character as an argument
--> tests/ui/split_with_space.rs:9:50
|
LL | for substr in some_space_delimtetered_string.split(' ') {
| ^ help: replace the use of `str::split` with `str::split_whitespace`
|
note: the lint level is defined here
--> tests/ui/split_with_space.rs:4:8
|
LL | #[deny(clippy::split_with_space)]
| ^^^^^^^^^^^^^^^^^^^^^^^^

error: found call to `str::split` that uses the ASCII space character as an argument
--> tests/ui/split_with_space.rs:13:50
|
LL | for substr in some_space_delimtetered_string.split(" ") {
| ^ help: replace the use of `str::split` with `str::split_whitespace`

error: aborting due to 2 previous errors

2 changes: 1 addition & 1 deletion tests/versioncheck.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn(rust_2018_idioms, unused_lifetimes)]
#![allow(clippy::single_match_else)]
#![allow(clippy::single_match_else, clippy::split_with_space)]

use std::fs;

Expand Down
Loading