Skip to content

Commit a867981

Browse files
Add new useless_concat lint
1 parent 2ce5451 commit a867981

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6438,6 +6438,7 @@ Released 2018-09-13
64386438
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
64396439
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
64406440
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
6441+
[`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat
64416442
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
64426443
[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
64436444
[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
762762
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
763763
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
764764
crate::use_self::USE_SELF_INFO,
765+
crate::useless_concat::USELESS_CONCAT_INFO,
765766
crate::useless_conversion::USELESS_CONVERSION_INFO,
766767
crate::vec::USELESS_VEC_INFO,
767768
crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ mod unwrap;
392392
mod unwrap_in_result;
393393
mod upper_case_acronyms;
394394
mod use_self;
395+
mod useless_concat;
395396
mod useless_conversion;
396397
mod vec;
397398
mod vec_init_then_push;
@@ -936,6 +937,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
936937
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
937938
store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new()));
938939
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
940+
store.register_late_pass(|_| Box::new(useless_concat::UselessConcat));
939941
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
940942
store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default());
941943
store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf)));

clippy_lints/src/useless_concat.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::macros::macro_backtrace;
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::{match_def_path, tokenize_with_text};
5+
use rustc_ast::LitKind;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_lexer::TokenKind;
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::declare_lint_pass;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks that the `concat!` macro has at least two arguments.
15+
///
16+
/// ### Why is this bad?
17+
/// If there are less than 2 arguments, then calling the macro is doing nothing.
18+
///
19+
/// ### Example
20+
/// ```no_run
21+
/// let x = concat!("a");
22+
/// ```
23+
/// Use instead:
24+
/// ```no_run
25+
/// let x = "a";
26+
/// ```
27+
#[clippy::version = "1.85.0"]
28+
pub USELESS_CONCAT,
29+
complexity,
30+
"checks that the `concat` macro has at least two arguments"
31+
}
32+
33+
declare_lint_pass!(UselessConcat => [USELESS_CONCAT]);
34+
35+
impl LateLintPass<'_> for UselessConcat {
36+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
37+
// Check that the expression is generated by a macro.
38+
if expr.span.from_expansion()
39+
// Check that it's a string literal.
40+
&& let ExprKind::Lit(lit) = expr.kind
41+
&& let LitKind::Str(_, _) = lit.node
42+
// Get the direct parent of the expression.
43+
&& let Some(macro_call) = macro_backtrace(expr.span).next()
44+
// Check if the `concat` macro from the `core` library.
45+
&& match_def_path(cx, macro_call.def_id, &["core", "macros", "builtin", "concat"])
46+
// We get the original code to parse it.
47+
&& let Some(original_code) = snippet_opt(cx, macro_call.span)
48+
// This check allows us to ensure that the code snippet:
49+
// 1. Doesn't come from proc-macro expansion.
50+
// 2. Doesn't come from foreign macro expansion.
51+
//
52+
// It works as follows: if the snippet we get doesn't contain `concat!(`, then it
53+
// means it's not code written in the current crate so we shouldn't lint.
54+
&& let mut parts = original_code.split('!')
55+
&& parts.next().is_some_and(|p| p.trim() == "concat")
56+
&& parts.next().is_some_and(|p| p.trim().starts_with('('))
57+
{
58+
let mut literal = None;
59+
let mut nb_commas = 0;
60+
let mut nb_idents = 0;
61+
for (token_kind, token_s, _) in tokenize_with_text(&original_code) {
62+
match token_kind {
63+
TokenKind::Eof => break,
64+
TokenKind::Literal { .. } => {
65+
if literal.is_some() {
66+
return;
67+
}
68+
literal = Some(token_s);
69+
},
70+
TokenKind::Ident => nb_idents += 1,
71+
TokenKind::Comma => {
72+
nb_commas += 1;
73+
if nb_commas > 1 {
74+
return;
75+
}
76+
},
77+
// We're inside a macro definition and we are manipulating something we likely
78+
// shouldn't, so aborting.
79+
TokenKind::Dollar => return,
80+
_ => {},
81+
}
82+
}
83+
let literal = match literal {
84+
Some(lit) => {
85+
// Literals can also be number, so we need to check this case too.
86+
if lit.starts_with('"') {
87+
lit.to_string()
88+
} else {
89+
format!("\"{lit}\"")
90+
}
91+
},
92+
None => "\"\"".to_string(),
93+
};
94+
// There should always be the ident of the `concat` macro.
95+
if nb_idents == 1 {
96+
span_lint_and_sugg(
97+
cx,
98+
USELESS_CONCAT,
99+
macro_call.span,
100+
"unneeded use of `concat!` macro",
101+
"replace with",
102+
literal,
103+
Applicability::MachineApplicable,
104+
);
105+
}
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)