mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
refactor(linter): Use parsed patterns in unicorn/prefer-string-starts-ends-with (#5949)
- part of https://github.com/oxc-project/oxc/issues/5416 This change enhances the accuracy of the `prefer_string_starts_ends_with` rule by using the parsed regex patterns for analysis. It allows for more precise detection of patterns that can be replaced with `startsWith()` and `endsWith()` methods, reducing false positives and improving the overall effectiveness of the linter. ### What changed? - Replaced the simple string-based regex analysis with a more robust AST-based approach. - Removed the `is_simple_string` function as it's no longer needed.
This commit is contained in:
parent
3273b64a0f
commit
05f592b834
1 changed files with 23 additions and 13 deletions
|
|
@ -4,6 +4,7 @@ use oxc_ast::{
|
|||
};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_regular_expression::ast::{BoundaryAssertionKind, Term};
|
||||
use oxc_span::{GetSpan, Span};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -146,26 +147,35 @@ fn check_regex(regexp_lit: &RegExpLiteral, pattern_text: &str) -> Option<ErrorKi
|
|||
return None;
|
||||
}
|
||||
|
||||
if pattern_text.starts_with('^')
|
||||
&& is_simple_string(&pattern_text[1..regexp_lit.regex.pattern.len()])
|
||||
{
|
||||
return Some(ErrorKind::StartsWith);
|
||||
let alternatives = regexp_lit.regex.pattern.as_pattern().map(|pattern| &pattern.body.body)?;
|
||||
// Must not be something with multiple alternatives like `/^a|b/`
|
||||
if alternatives.len() > 1 {
|
||||
return None;
|
||||
}
|
||||
let pattern_terms = alternatives.first().map(|it| &it.body)?;
|
||||
|
||||
if let Some(Term::BoundaryAssertion(boundary_assert)) = pattern_terms.first() {
|
||||
if boundary_assert.kind == BoundaryAssertionKind::Start
|
||||
&& pattern_terms.iter().skip(1).all(|term| matches!(term, Term::Character(_)))
|
||||
{
|
||||
return Some(ErrorKind::StartsWith);
|
||||
}
|
||||
}
|
||||
|
||||
if pattern_text.ends_with('$')
|
||||
&& is_simple_string(&pattern_text[0..regexp_lit.regex.pattern.len() - 1])
|
||||
{
|
||||
return Some(ErrorKind::EndsWith);
|
||||
if let Some(Term::BoundaryAssertion(boundary_assert)) = pattern_terms.last() {
|
||||
if boundary_assert.kind == BoundaryAssertionKind::End
|
||||
&& pattern_terms
|
||||
.iter()
|
||||
.take(pattern_terms.len() - 1)
|
||||
.all(|term| matches!(term, Term::Character(_)))
|
||||
{
|
||||
return Some(ErrorKind::EndsWith);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_simple_string(str: &str) -> bool {
|
||||
str.chars()
|
||||
.all(|c| !matches!(c, '^' | '$' | '+' | '[' | '{' | '(' | '\\' | '.' | '?' | '*' | '|'))
|
||||
}
|
||||
|
||||
// `/^#/i` => `true` (the `i` flag is useless)
|
||||
// `/^foo/i` => `false` (the `i` flag is not useless)
|
||||
fn is_useless_case_sensitive_regex_flag(pattern_text: &str) -> bool {
|
||||
|
|
|
|||
Loading…
Reference in a new issue