mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): add fixer for unicorn/prefer-string-starts-ends-with (#4378)
Part of #4179
This commit is contained in:
parent
a207923af1
commit
51f5025c9c
2 changed files with 91 additions and 5 deletions
|
|
@ -1,12 +1,17 @@
|
||||||
use oxc_ast::{
|
use oxc_ast::{
|
||||||
ast::{Expression, MemberExpression, RegExpFlags, RegExpLiteral},
|
ast::{CallExpression, Expression, MemberExpression, RegExpFlags, RegExpLiteral},
|
||||||
AstKind,
|
AstKind,
|
||||||
};
|
};
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_macros::declare_oxc_lint;
|
use oxc_macros::declare_oxc_lint;
|
||||||
use oxc_span::{GetSpan, Span};
|
use oxc_span::{GetSpan, Span};
|
||||||
|
|
||||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
use crate::{
|
||||||
|
context::LintContext,
|
||||||
|
fixer::{RuleFix, RuleFixer},
|
||||||
|
rule::Rule,
|
||||||
|
AstNode,
|
||||||
|
};
|
||||||
|
|
||||||
fn starts_with(span0: Span) -> OxcDiagnostic {
|
fn starts_with(span0: Span) -> OxcDiagnostic {
|
||||||
OxcDiagnostic::warn("Prefer String#startsWith over a regex with a caret.").with_label(span0)
|
OxcDiagnostic::warn("Prefer String#startsWith over a regex with a caret.").with_label(span0)
|
||||||
|
|
@ -74,15 +79,54 @@ impl Rule for PreferStringStartsEndsWith {
|
||||||
|
|
||||||
match err_kind {
|
match err_kind {
|
||||||
ErrorKind::StartsWith => {
|
ErrorKind::StartsWith => {
|
||||||
ctx.diagnostic(starts_with(member_expr.span()));
|
ctx.diagnostic_with_fix(starts_with(member_expr.span()), |fixer| {
|
||||||
|
do_fix(fixer, err_kind, call_expr, regex)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ErrorKind::EndsWith => {
|
ErrorKind::EndsWith => {
|
||||||
ctx.diagnostic(ends_with(member_expr.span()));
|
ctx.diagnostic_with_fix(ends_with(member_expr.span()), |fixer| {
|
||||||
|
do_fix(fixer, err_kind, call_expr, regex)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_fix<'a>(
|
||||||
|
fixer: RuleFixer<'_, 'a>,
|
||||||
|
err_kind: ErrorKind,
|
||||||
|
call_expr: &CallExpression<'a>,
|
||||||
|
regex: &RegExpLiteral,
|
||||||
|
) -> RuleFix<'a> {
|
||||||
|
let Some(target_span) = can_replace(call_expr) else { return fixer.noop() };
|
||||||
|
let pattern = ®ex.regex.pattern;
|
||||||
|
let (argument, method) = match err_kind {
|
||||||
|
ErrorKind::StartsWith => (pattern.trim_start_matches('^'), "startsWith"),
|
||||||
|
ErrorKind::EndsWith => (pattern.trim_end_matches('$'), "endsWith"),
|
||||||
|
};
|
||||||
|
let fix_text = format!(r#"{}.{}("{}")"#, fixer.source_range(target_span), method, argument);
|
||||||
|
|
||||||
|
fixer.replace(call_expr.span, fix_text)
|
||||||
|
}
|
||||||
|
fn can_replace(call_expr: &CallExpression) -> Option<Span> {
|
||||||
|
if call_expr.arguments.len() != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = &call_expr.arguments[0];
|
||||||
|
let expr = arg.as_expression()?;
|
||||||
|
match expr.without_parenthesized() {
|
||||||
|
Expression::StringLiteral(s) => Some(s.span),
|
||||||
|
Expression::TemplateLiteral(s) => Some(s.span),
|
||||||
|
Expression::Identifier(ident) => Some(ident.span),
|
||||||
|
Expression::StaticMemberExpression(m) => Some(m.span),
|
||||||
|
Expression::ComputedMemberExpression(m) => Some(m.span),
|
||||||
|
Expression::CallExpression(c) => Some(c.span),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
enum ErrorKind {
|
enum ErrorKind {
|
||||||
StartsWith,
|
StartsWith,
|
||||||
EndsWith,
|
EndsWith,
|
||||||
|
|
@ -211,5 +255,24 @@ fn test() {
|
||||||
r"if (/#$/i.test(hex)) {}",
|
r"if (/#$/i.test(hex)) {}",
|
||||||
];
|
];
|
||||||
|
|
||||||
Tester::new(PreferStringStartsEndsWith::NAME, pass, fail).test_and_snapshot();
|
let fix = vec![
|
||||||
|
("/^foo/.test(x)", r#"x.startsWith("foo")"#, None),
|
||||||
|
("/foo$/.test(x)", r#"x.endsWith("foo")"#, None),
|
||||||
|
("/^foo/.test(x.y)", r#"x.y.startsWith("foo")"#, None),
|
||||||
|
("/foo$/.test(x.y)", r#"x.y.endsWith("foo")"#, None),
|
||||||
|
("/^foo/.test('x')", r#"'x'.startsWith("foo")"#, None),
|
||||||
|
("/foo$/.test('x')", r#"'x'.endsWith("foo")"#, None),
|
||||||
|
("/^foo/.test(`x${y}`)", r#"`x${y}`.startsWith("foo")"#, None),
|
||||||
|
("/foo$/.test(`x${y}`)", r#"`x${y}`.endsWith("foo")"#, None),
|
||||||
|
("/^foo/.test(String(x))", r#"String(x).startsWith("foo")"#, None),
|
||||||
|
("/foo$/.test(String(x))", r#"String(x).endsWith("foo")"#, None),
|
||||||
|
// should not get fixed
|
||||||
|
("/^foo/.test(new String('bar'))", "/^foo/.test(new String('bar'))", None),
|
||||||
|
("/^foo/.test(x as string)", "/^foo/.test(x as string)", None),
|
||||||
|
("/^foo/.test(5)", "/^foo/.test(5)", None),
|
||||||
|
("/^foo/.test(x?.y)", "/^foo/.test(x?.y)", None),
|
||||||
|
("/^foo/.test(x + y)", "/^foo/.test(x + y)", None),
|
||||||
|
];
|
||||||
|
|
||||||
|
Tester::new(PreferStringStartsEndsWith::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,66 +6,77 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
1 │ /^foo/.test(bar)
|
1 │ /^foo/.test(bar)
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^foo/.test(bar)` with `bar.startsWith("foo")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /foo$/.test(bar)
|
1 │ /foo$/.test(bar)
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/foo$/.test(bar)` with `bar.endsWith("foo")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /^!/.test(bar)
|
1 │ /^!/.test(bar)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^!/.test(bar)` with `bar.startsWith("!")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /!$/.test(bar)
|
1 │ /!$/.test(bar)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/!$/.test(bar)` with `bar.endsWith("!")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /^ /.test(bar)
|
1 │ /^ /.test(bar)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^ /.test(bar)` with `bar.startsWith(" ")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ / $/.test(bar)
|
1 │ / $/.test(bar)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/ $/.test(bar)` with `bar.endsWith(" ")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:17]
|
╭─[prefer_string_starts_ends_with.tsx:1:17]
|
||||||
1 │ const foo = {}; /^abc/.test(foo);
|
1 │ const foo = {}; /^abc/.test(foo);
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^abc/.test(foo)` with `foo.startsWith("abc")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:18]
|
╭─[prefer_string_starts_ends_with.tsx:1:18]
|
||||||
1 │ const foo = 123; /^abc/.test(foo);
|
1 │ const foo = 123; /^abc/.test(foo);
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^abc/.test(foo)` with `foo.startsWith("abc")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:22]
|
╭─[prefer_string_starts_ends_with.tsx:1:22]
|
||||||
1 │ const foo = "hello"; /^abc/.test(foo);
|
1 │ const foo = "hello"; /^abc/.test(foo);
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^abc/.test(foo)` with `foo.startsWith("abc")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /^b/.test((a))
|
1 │ /^b/.test((a))
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^b/.test((a))` with `a.startsWith("b")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ (/^b/).test((a))
|
1 │ (/^b/).test((a))
|
||||||
· ───────────
|
· ───────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `(/^b/).test((a))` with `a.startsWith("b")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:24]
|
╭─[prefer_string_starts_ends_with.tsx:1:24]
|
||||||
|
|
@ -84,6 +95,7 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
1 │ /^a/.test("string")
|
1 │ /^a/.test("string")
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/.test("string")` with `"string".startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
|
|
@ -150,12 +162,14 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
1 │ /^a/.test(foo.bar)
|
1 │ /^a/.test(foo.bar)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/.test(foo.bar)` with `foo.bar.startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /^a/.test(foo.bar())
|
1 │ /^a/.test(foo.bar())
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/.test(foo.bar())` with `foo.bar().startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
|
|
@ -174,6 +188,7 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
1 │ /^a/.test(`string`)
|
1 │ /^a/.test(`string`)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/.test(`string`)` with ``string`.startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
|
|
@ -216,45 +231,53 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
1 │ /^a/u.test("string")
|
1 │ /^a/u.test("string")
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/u.test("string")` with `"string".startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /^a/v.test("string")
|
1 │ /^a/v.test("string")
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^a/v.test("string")` with `"string".startsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /a$/.test(`${unknown}`)
|
1 │ /a$/.test(`${unknown}`)
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/a$/.test(`${unknown}`)` with ``${unknown}`.endsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
╭─[prefer_string_starts_ends_with.tsx:1:1]
|
||||||
1 │ /a$/.test(String(unknown))
|
1 │ /a$/.test(String(unknown))
|
||||||
· ─────────
|
· ─────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/a$/.test(String(unknown))` with `String(unknown).endsWith("a")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:11]
|
╭─[prefer_string_starts_ends_with.tsx:1:11]
|
||||||
1 │ const a = /你$/.test('a');
|
1 │ const a = /你$/.test('a');
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/你$/.test('a')` with `'a'.endsWith("你")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:11]
|
╭─[prefer_string_starts_ends_with.tsx:1:11]
|
||||||
1 │ const a = /^你/.test('a');
|
1 │ const a = /^你/.test('a');
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^你/.test('a')` with `'a'.startsWith("你")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#startsWith over a regex with a caret.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:5]
|
╭─[prefer_string_starts_ends_with.tsx:1:5]
|
||||||
1 │ if (/^#/i.test(hex)) {}
|
1 │ if (/^#/i.test(hex)) {}
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/^#/i.test(hex)` with `hex.startsWith("#")`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
⚠ eslint-plugin-unicorn(prefer-string-starts-ends-with): Prefer String#endsWith over a regex with a dollar sign.
|
||||||
╭─[prefer_string_starts_ends_with.tsx:1:5]
|
╭─[prefer_string_starts_ends_with.tsx:1:5]
|
||||||
1 │ if (/#$/i.test(hex)) {}
|
1 │ if (/#$/i.test(hex)) {}
|
||||||
· ──────────
|
· ──────────
|
||||||
╰────
|
╰────
|
||||||
|
help: Replace `/#$/i.test(hex)` with `hex.endsWith("#")`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue