Skip to content

Commit 35f92f6

Browse files
Add new useless_concat lint
1 parent f2aed50 commit 35f92f6

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6170,6 +6170,7 @@ Released 2018-09-13
61706170
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
61716171
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
61726172
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
6173+
[`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat
61736174
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
61746175
[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
61756176
[`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
@@ -770,6 +770,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
770770
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
771771
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
772772
crate::use_self::USE_SELF_INFO,
773+
crate::useless_concat::USELESS_CONCAT_INFO,
773774
crate::useless_conversion::USELESS_CONVERSION_INFO,
774775
crate::vec::USELESS_VEC_INFO,
775776
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
@@ -388,6 +388,7 @@ mod unwrap;
388388
mod unwrap_in_result;
389389
mod upper_case_acronyms;
390390
mod use_self;
391+
mod useless_concat;
391392
mod useless_conversion;
392393
mod vec;
393394
mod vec_init_then_push;
@@ -967,5 +968,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
967968
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
968969
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
969970
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
971+
store.register_late_pass(|_| Box::new(useless_concat::UselessConcat));
970972
// add lints here, do not remove this comment, it's used in `new_lint`
971973
}

clippy_lints/src/useless_concat.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::macros::macro_backtrace;
3+
use clippy_utils::match_def_path;
4+
use clippy_utils::source::snippet_opt;
5+
use rustc_ast::LitKind;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::*;
8+
use rustc_lexer::{Cursor, 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+
suspicious,
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, expr.span)
48+
{
49+
let mut literal = None;
50+
let mut current_pos = 0;
51+
let mut cursor = Cursor::new(&original_code);
52+
loop {
53+
let token = cursor.advance_token();
54+
match token.kind {
55+
TokenKind::Eof => break,
56+
TokenKind::Literal { .. } => {
57+
if literal.is_some() {
58+
return;
59+
}
60+
literal = Some(&original_code[current_pos..current_pos + token.len as usize]);
61+
},
62+
// We're inside a macro definition and we are manipulating something we likely
63+
// shouldn't so aborting.
64+
TokenKind::Dollar => return,
65+
_ => {},
66+
}
67+
current_pos += token.len as usize;
68+
}
69+
span_lint_and_sugg(
70+
cx,
71+
USELESS_CONCAT,
72+
macro_call.span,
73+
"unneeded use of `concat!` macro",
74+
"replace with",
75+
literal.unwrap_or("\"\"").into(),
76+
Applicability::MachineApplicable,
77+
);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)