From 05f592b834e32870711566815996dd0ece5f2882 Mon Sep 17 00:00:00 2001 From: camchenry <1514176+camchenry@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:15:07 +0000 Subject: [PATCH] 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. --- .../unicorn/prefer_string_starts_ends_with.rs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs index 0986578567417..359ed4cbc1167 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs @@ -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,24 +147,33 @@ fn check_regex(regexp_lit: &RegExpLiteral, pattern_text: &str) -> Option 1 { + return None; } + let pattern_terms = alternatives.first().map(|it| &it.body)?; - 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.first() { + if boundary_assert.kind == BoundaryAssertionKind::Start + && pattern_terms.iter().skip(1).all(|term| matches!(term, Term::Character(_))) + { + return Some(ErrorKind::StartsWith); + } } - None -} + 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); + } + } -fn is_simple_string(str: &str) -> bool { - str.chars() - .all(|c| !matches!(c, '^' | '$' | '+' | '[' | '{' | '(' | '\\' | '.' | '?' | '*' | '|')) + None } // `/^#/i` => `true` (the `i` flag is useless)