mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +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_private_class_members;
|
||||
pub mod no_useless_catch;
|
||||
pub mod no_useless_concat;
|
||||
pub mod no_useless_escape;
|
||||
pub mod no_useless_rename;
|
||||
pub mod no_var;
|
||||
|
|
@ -468,6 +469,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
eslint::no_useless_catch,
|
||||
eslint::no_useless_escape,
|
||||
eslint::no_useless_rename,
|
||||
eslint::no_useless_concat,
|
||||
eslint::no_var,
|
||||
eslint::no_void,
|
||||
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