mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter/eslint): Implement no-useless-concat (#3363)
Rule Detail: [link](https://eslint.org/docs/latest/rules/no-useless-concat) --- I haven't implemented one condition with the multiline string test case and honestly I don't really understand why it is allowed? And also not sure how I would implement that in oxlint. Another issue is that maybe the output isn't great, the underlining matches the whole BinaryExpression, for example: ``` + ╭─[no_useless_concat.tsx:1:1] + 1 │ foo + `a` + `b` + · ─────────────── ``` So maybe instead the diagnostic should get two spans passed, for each Expression::StringLiteral or Expression::TemplateLiteral, that would also allow the help text to show that it can be written as `"ab"`. But an automatic fixxer would be more helpful I reckon :) --------- Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
7d73fb33e8
commit
147864cfeb
3 changed files with 248 additions and 0 deletions
|
|
@ -99,6 +99,7 @@ mod eslint {
|
||||||
pub mod no_unused_labels;
|
pub mod no_unused_labels;
|
||||||
pub mod no_unused_private_class_members;
|
pub mod no_unused_private_class_members;
|
||||||
pub mod no_useless_catch;
|
pub mod no_useless_catch;
|
||||||
|
pub mod no_useless_concat;
|
||||||
pub mod no_useless_escape;
|
pub mod no_useless_escape;
|
||||||
pub mod no_useless_rename;
|
pub mod no_useless_rename;
|
||||||
pub mod no_var;
|
pub mod no_var;
|
||||||
|
|
@ -468,6 +469,7 @@ oxc_macros::declare_all_lint_rules! {
|
||||||
eslint::no_useless_catch,
|
eslint::no_useless_catch,
|
||||||
eslint::no_useless_escape,
|
eslint::no_useless_escape,
|
||||||
eslint::no_useless_rename,
|
eslint::no_useless_rename,
|
||||||
|
eslint::no_useless_concat,
|
||||||
eslint::no_var,
|
eslint::no_var,
|
||||||
eslint::no_void,
|
eslint::no_void,
|
||||||
eslint::no_with,
|
eslint::no_with,
|
||||||
|
|
|
||||||
135
crates/oxc_linter/src/rules/eslint/no_useless_concat.rs
Normal file
135
crates/oxc_linter/src/rules/eslint/no_useless_concat.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use oxc_ast::ast::{BinaryExpression, Expression};
|
||||||
|
use oxc_ast::AstKind;
|
||||||
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
|
use oxc_macros::declare_oxc_lint;
|
||||||
|
use oxc_span::{GetSpan, Span};
|
||||||
|
use oxc_syntax::{identifier::is_line_terminator, operator::BinaryOperator};
|
||||||
|
|
||||||
|
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct NoUselessConcat;
|
||||||
|
|
||||||
|
fn no_useless_concat_diagnostic(span0: Span) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn("eslint(no-useless-concat): Unexpected string concatenation of literals.")
|
||||||
|
.with_help("Rewrite into one string literal")
|
||||||
|
.with_labels([span0.into()])
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_oxc_lint!(
|
||||||
|
/// ### What it does
|
||||||
|
///
|
||||||
|
/// Disallow unnecessary concatenation of literals or template literals
|
||||||
|
///
|
||||||
|
/// ### Why is this bad?
|
||||||
|
///
|
||||||
|
/// It’s unnecessary to concatenate two strings together.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
/// ```javascript
|
||||||
|
/// var foo = "a" + "b";
|
||||||
|
/// ```
|
||||||
|
NoUselessConcat,
|
||||||
|
suspicious
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Rule for NoUselessConcat {
|
||||||
|
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||||
|
let AstKind::BinaryExpression(binary_expr) = node.kind() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if binary_expr.operator != BinaryOperator::Addition {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = get_left(binary_expr);
|
||||||
|
let right = get_right(binary_expr);
|
||||||
|
|
||||||
|
if left.is_string_literal() && right.is_string_literal() {
|
||||||
|
let left_span = left.span();
|
||||||
|
let right_span = right.span();
|
||||||
|
let span = Span::new(left_span.end, right_span.start);
|
||||||
|
let source_text = span.source_text(ctx.source_text());
|
||||||
|
if source_text.chars().any(is_line_terminator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let span = Span::new(left_span.start, right_span.end);
|
||||||
|
ctx.diagnostic(no_useless_concat_diagnostic(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_left<'a>(expr: &'a BinaryExpression<'a>) -> &'a Expression<'a> {
|
||||||
|
let mut left = &expr.left;
|
||||||
|
loop {
|
||||||
|
if let Expression::BinaryExpression(binary_expr) = left {
|
||||||
|
if binary_expr.operator == BinaryOperator::Addition {
|
||||||
|
left = &binary_expr.right;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_right<'a>(expr: &'a BinaryExpression<'a>) -> &'a Expression<'a> {
|
||||||
|
let mut right = &expr.right;
|
||||||
|
loop {
|
||||||
|
if let Expression::BinaryExpression(binary_expr) = right {
|
||||||
|
if binary_expr.operator == BinaryOperator::Addition {
|
||||||
|
right = &binary_expr.left;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
use crate::tester::Tester;
|
||||||
|
|
||||||
|
let pass = vec![
|
||||||
|
"var a = 1 + 1;",
|
||||||
|
"var a = 1 * '2';",
|
||||||
|
"var a = 1 - 2;",
|
||||||
|
"var a = foo + bar;",
|
||||||
|
"var a = 'foo' + bar;",
|
||||||
|
"var foo = 'foo' +
|
||||||
|
'bar';",
|
||||||
|
"var string = (number + 1) + 'px';",
|
||||||
|
"'a' + 1",
|
||||||
|
"1 + '1'",
|
||||||
|
"1 + `1`",
|
||||||
|
"`1` + 1",
|
||||||
|
"(1 + +2) + `b`",
|
||||||
|
"
|
||||||
|
'a'
|
||||||
|
+ 'b'
|
||||||
|
+ 'c'
|
||||||
|
",
|
||||||
|
];
|
||||||
|
|
||||||
|
let fail = vec![
|
||||||
|
"'a' + 'b'",
|
||||||
|
"'a' +
|
||||||
|
'b' + 'c'",
|
||||||
|
"foo + 'a' + 'b'",
|
||||||
|
"'a' + 'b' + 'c'",
|
||||||
|
"(foo + 'a') + ('b' + 'c')",
|
||||||
|
"`a` + 'b'",
|
||||||
|
"`a` + `b`",
|
||||||
|
"foo + `a` + `b`",
|
||||||
|
"foo + 'a' + 'b'",
|
||||||
|
"'a' +
|
||||||
|
'b' + 'c'
|
||||||
|
+ 'd'
|
||||||
|
",
|
||||||
|
"'a' + 'b' + 'c' + 'd' + 'e' + foo",
|
||||||
|
];
|
||||||
|
|
||||||
|
Tester::new(NoUselessConcat::NAME, pass, fail).test_and_snapshot();
|
||||||
|
}
|
||||||
111
crates/oxc_linter/src/snapshots/no_useless_concat.snap
Normal file
111
crates/oxc_linter/src/snapshots/no_useless_concat.snap
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
---
|
||||||
|
source: crates/oxc_linter/src/tester.rs
|
||||||
|
expression: no_useless_concat
|
||||||
|
---
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:1]
|
||||||
|
1 │ 'a' + 'b'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:2:9]
|
||||||
|
1 │ 'a' +
|
||||||
|
2 │ 'b' + 'c'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:7]
|
||||||
|
1 │ foo + 'a' + 'b'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:7]
|
||||||
|
1 │ 'a' + 'b' + 'c'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:1]
|
||||||
|
1 │ 'a' + 'b' + 'c'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:16]
|
||||||
|
1 │ (foo + 'a') + ('b' + 'c')
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:1]
|
||||||
|
1 │ `a` + 'b'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:1]
|
||||||
|
1 │ `a` + `b`
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:7]
|
||||||
|
1 │ foo + `a` + `b`
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:7]
|
||||||
|
1 │ foo + 'a' + 'b'
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:2:9]
|
||||||
|
1 │ 'a' +
|
||||||
|
2 │ 'b' + 'c'
|
||||||
|
· ─────────
|
||||||
|
3 │ + 'd'
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:19]
|
||||||
|
1 │ 'a' + 'b' + 'c' + 'd' + 'e' + foo
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:13]
|
||||||
|
1 │ 'a' + 'b' + 'c' + 'd' + 'e' + foo
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:7]
|
||||||
|
1 │ 'a' + 'b' + 'c' + 'd' + 'e' + foo
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-concat): Unexpected string concatenation of literals.
|
||||||
|
╭─[no_useless_concat.tsx:1:1]
|
||||||
|
1 │ 'a' + 'b' + 'c' + 'd' + 'e' + foo
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Rewrite into one string literal
|
||||||
Loading…
Reference in a new issue