Skip to content
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

Add new useless_concat lint #13829

Open
wants to merge 2 commits 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 @@ -6170,6 +6170,7 @@ Released 2018-09-13
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
[`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq
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 @@ -770,6 +770,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
crate::use_self::USE_SELF_INFO,
crate::useless_concat::USELESS_CONCAT_INFO,
crate::useless_conversion::USELESS_CONVERSION_INFO,
crate::vec::USELESS_VEC_INFO,
crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ mod unwrap;
mod unwrap_in_result;
mod upper_case_acronyms;
mod use_self;
mod useless_concat;
mod useless_conversion;
mod vec;
mod vec_init_then_push;
Expand Down Expand Up @@ -967,5 +968,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
store.register_late_pass(|_| Box::new(useless_concat::UselessConcat));
// add lints here, do not remove this comment, it's used in `new_lint`
}
101 changes: 101 additions & 0 deletions clippy_lints/src/useless_concat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::macro_backtrace;
use clippy_utils::match_def_path;
use clippy_utils::source::snippet_opt;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lexer::{Cursor, TokenKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

declare_clippy_lint! {
/// ### What it does
/// Checks that the `concat!` macro has at least two arguments.
///
/// ### Why is this bad?
/// If there are less than 2 arguments, then calling the macro is doing nothing.
///
/// ### Example
/// ```no_run
/// let x = concat!("a");
/// ```
/// Use instead:
/// ```no_run
/// let x = "a";
/// ```
#[clippy::version = "1.85.0"]
pub USELESS_CONCAT,
suspicious,
"checks that the `concat` macro has at least two arguments"
}

declare_lint_pass!(UselessConcat => [USELESS_CONCAT]);

impl LateLintPass<'_> for UselessConcat {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// Check that the expression is generated by a macro.
if expr.span.from_expansion()
// Check that it's a string literal.
&& let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(_, _) = lit.node
// Get the direct parent of the expression.
&& let Some(macro_call) = macro_backtrace(expr.span).next()
// Check if the `concat` macro from the `core` library.
&& match_def_path(cx, macro_call.def_id, &["core", "macros", "builtin", "concat"])
// We get the original code to parse it.
&& let Some(original_code) = snippet_opt(cx, macro_call.span)
// This check allows us to ensure that the code snippet:
// 1. Doesn't come from proc-macro expansion.
// 2. Doesn't come from foreign macro expansion.
//
// It works as follows: if the snippet we get doesn't contain `concat!(`, then it
// means it's not code written in the current crate so we shouldn't lint.
&& let mut parts = original_code.split('!')
&& parts.next().is_some_and(|p| p.trim() == "concat")
&& parts.next().is_some_and(|p| p.trim().starts_with('('))
{
let mut literal = None;
let mut current_pos = 0;
let mut cursor = Cursor::new(&original_code);
let mut nb_commas = 0;
let mut nb_idents = 0;
loop {
let token = cursor.advance_token();
match token.kind {
TokenKind::Eof => break,
TokenKind::Literal { .. } => {
if literal.is_some() {
return;
}
literal = Some(&original_code[current_pos..current_pos + token.len as usize]);
},
TokenKind::Ident => nb_idents += 1,
TokenKind::Comma => {
nb_commas += 1;
if nb_commas > 1 {
return;
}
},
// We're inside a macro definition and we are manipulating something we likely
// shouldn't so aborting.
TokenKind::Dollar => return,
_ => {},
}
current_pos += token.len as usize;
}
// There should always be the ident of the `concat` macro.
if nb_idents == 1 {
span_lint_and_sugg(
cx,
USELESS_CONCAT,
macro_call.span,
"unneeded use of `concat!` macro",
"replace with",
literal.unwrap_or("\"\"").into(),
Applicability::MachineApplicable,
);
}
}
}
}
19 changes: 19 additions & 0 deletions tests/ui/useless_concat.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::useless_concat)]
#![allow(clippy::print_literal)]

macro_rules! my_concat {
($fmt:literal $(, $e:expr)*) => {
println!(concat!("ERROR: ", $fmt), $($e,)*);
}
}

fn main() {
let x = ""; //~ useless_concat
let x = "a"; //~ useless_concat
println!("b: {}", "a"); //~ useless_concat
// Should not lint.
let x = concat!("a", "b");
let local_i32 = 1;
my_concat!("{}", local_i32);
let x = concat!(file!(), "#L", line!());
}
19 changes: 19 additions & 0 deletions tests/ui/useless_concat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::useless_concat)]
#![allow(clippy::print_literal)]

macro_rules! my_concat {
($fmt:literal $(, $e:expr)*) => {
println!(concat!("ERROR: ", $fmt), $($e,)*);
}
}

fn main() {
let x = concat!(); //~ useless_concat
let x = concat!("a"); //~ useless_concat
println!("b: {}", concat!("a")); //~ useless_concat
// Should not lint.
let x = concat!("a", "b");
let local_i32 = 1;
my_concat!("{}", local_i32);
let x = concat!(file!(), "#L", line!());
}
23 changes: 23 additions & 0 deletions tests/ui/useless_concat.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error: unneeded use of `concat!` macro
--> tests/ui/useless_concat.rs:11:13
|
LL | let x = concat!();
| ^^^^^^^^^ help: replace with: `""`
|
= note: `-D clippy::useless-concat` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::useless_concat)]`

error: unneeded use of `concat!` macro
--> tests/ui/useless_concat.rs:12:13
|
LL | let x = concat!("a");
| ^^^^^^^^^^^^ help: replace with: `"a"`

error: unneeded use of `concat!` macro
--> tests/ui/useless_concat.rs:13:23
|
LL | println!("b: {}", concat!("a"));
| ^^^^^^^^^^^^ help: replace with: `"a"`

error: aborting due to 3 previous errors

Loading