Skip to content

Commit 2efe3f0

Browse files
committed
refactor(linter): move run on regex node to utils (#10772)
1 parent 3de1b31 commit 2efe3f0

File tree

3 files changed

+94
-73
lines changed

3 files changed

+94
-73
lines changed

crates/oxc_linter/src/rules/eslint/no_control_regex.rs

Lines changed: 5 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
use itertools::Itertools as _;
2-
use oxc_allocator::Allocator;
3-
use oxc_ast::{AstKind, ast::Argument};
42
use oxc_diagnostics::OxcDiagnostic;
53
use oxc_macros::declare_oxc_lint;
64
use oxc_regular_expression::{
7-
ConstructorParser, Options,
85
ast::{CapturingGroup, Character, Pattern},
96
visit::{Visit, walk},
107
};
118
use oxc_span::Span;
129

13-
use crate::{AstNode, context::LintContext, rule::Rule};
10+
use crate::{AstNode, context::LintContext, rule::Rule, utils::run_on_regex_node};
1411

1512
fn no_control_regex_diagnostic(count: usize, regex: &str, span: Span) -> OxcDiagnostic {
1613
debug_assert!(count > 0);
@@ -71,77 +68,13 @@ declare_oxc_lint!(
7168
);
7269

7370
impl Rule for NoControlRegex {
74-
fn run<'a>(&self, node: &AstNode<'a>, context: &LintContext<'a>) {
75-
match node.kind() {
76-
// regex literal
77-
AstKind::RegExpLiteral(reg) => {
78-
let Some(pattern) = reg.regex.pattern.as_pattern() else {
79-
return;
80-
};
81-
82-
check_pattern(context, pattern, reg.span);
83-
}
84-
85-
// new RegExp()
86-
AstKind::NewExpression(expr) if expr.callee.is_specific_id("RegExp") => {
87-
// note: improvements required for strings used via identifier references
88-
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
89-
match (&expr.arguments.first(), &expr.arguments.get(1)) {
90-
(
91-
Some(Argument::StringLiteral(pattern)),
92-
Some(Argument::StringLiteral(flags)),
93-
) => {
94-
parse_and_check_regex(context, pattern.span, Some(flags.span));
95-
}
96-
(Some(Argument::StringLiteral(pattern)), _) => {
97-
parse_and_check_regex(context, pattern.span, None);
98-
}
99-
_ => {}
100-
}
101-
}
102-
103-
// RegExp()
104-
AstKind::CallExpression(expr) if expr.callee.is_specific_id("RegExp") => {
105-
// note: improvements required for strings used via identifier references
106-
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
107-
match (&expr.arguments.first(), &expr.arguments.get(1)) {
108-
(
109-
Some(Argument::StringLiteral(pattern)),
110-
Some(Argument::StringLiteral(flags)),
111-
) => {
112-
parse_and_check_regex(context, pattern.span, Some(flags.span));
113-
}
114-
(Some(Argument::StringLiteral(pattern)), _) => {
115-
parse_and_check_regex(context, pattern.span, None);
116-
}
117-
_ => {}
118-
}
119-
}
120-
121-
_ => {}
122-
}
71+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
72+
run_on_regex_node(node, ctx, |pattern, span| {
73+
check_pattern(ctx, pattern, span);
74+
});
12375
}
12476
}
12577

126-
fn parse_and_check_regex(ctx: &LintContext, pattern_span: Span, flags_span: Option<Span>) {
127-
let allocator = Allocator::default();
128-
129-
let flags_text = flags_span.map(|span| span.source_text(ctx.source_text()));
130-
let parser = ConstructorParser::new(
131-
&allocator,
132-
pattern_span.source_text(ctx.source_text()),
133-
flags_text,
134-
Options {
135-
pattern_span_offset: pattern_span.start,
136-
flags_span_offset: flags_span.map_or(0, |span| span.start),
137-
},
138-
);
139-
let Ok(pattern) = parser.parse() else {
140-
return;
141-
};
142-
check_pattern(ctx, &pattern, pattern_span);
143-
}
144-
14578
fn check_pattern(context: &LintContext, pattern: &Pattern, span: Span) {
14679
let mut finder = ControlCharacterFinder {
14780
control_chars: Vec::new(),

crates/oxc_linter/src/utils/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod nextjs;
77
mod promise;
88
mod react;
99
mod react_perf;
10+
mod regex;
1011
mod unicorn;
1112
mod url;
1213
mod vitest;
@@ -15,7 +16,7 @@ use std::{io, path::Path};
1516

1617
pub use self::{
1718
comment::*, config::*, express::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*,
18-
react_perf::*, unicorn::*, url::*, vitest::*,
19+
react_perf::*, regex::*, unicorn::*, url::*, vitest::*,
1920
};
2021

2122
/// List of Jest rules that have Vitest equivalents.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use oxc_allocator::Allocator;
2+
use oxc_ast::{AstKind, ast::Argument};
3+
use oxc_regular_expression::{ConstructorParser, Options, ast::Pattern};
4+
use oxc_semantic::IsGlobalReference;
5+
use oxc_span::Span;
6+
7+
use crate::{AstNode, context::LintContext};
8+
9+
pub fn run_on_regex_node<'a, 'b, M>(node: &'a AstNode<'b>, ctx: &'a LintContext<'b>, cb: M)
10+
where
11+
M: FnOnce(&Pattern<'_>, Span),
12+
{
13+
match node.kind() {
14+
AstKind::RegExpLiteral(reg) => {
15+
if let Some(pat) = reg.regex.pattern.as_pattern() {
16+
cb(pat, reg.span);
17+
}
18+
}
19+
AstKind::NewExpression(expr)
20+
if expr.callee.is_global_reference_name("RegExp", ctx.semantic().scoping()) =>
21+
{
22+
// note: improvements required for strings used via identifier references
23+
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
24+
match (&expr.arguments.first(), &expr.arguments.get(1)) {
25+
(Some(Argument::StringLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
26+
let allocator = Allocator::default();
27+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx)
28+
{
29+
cb(&pat, pattern.span);
30+
}
31+
}
32+
(Some(Argument::StringLiteral(pattern)), _) => {
33+
let allocator = Allocator::default();
34+
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
35+
cb(&pat, pattern.span);
36+
}
37+
}
38+
_ => {}
39+
}
40+
}
41+
42+
// RegExp()
43+
AstKind::CallExpression(expr)
44+
if expr.callee.is_global_reference_name("RegExp", ctx.semantic().scoping()) =>
45+
{
46+
// note: improvements required for strings used via identifier references
47+
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
48+
match (&expr.arguments.first(), &expr.arguments.get(1)) {
49+
(Some(Argument::StringLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
50+
let allocator = Allocator::default();
51+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx)
52+
{
53+
cb(&pat, pattern.span);
54+
}
55+
}
56+
(Some(Argument::StringLiteral(pattern)), _) => {
57+
let allocator = Allocator::default();
58+
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
59+
cb(&pat, pattern.span);
60+
}
61+
}
62+
_ => {}
63+
}
64+
}
65+
_ => {}
66+
}
67+
}
68+
69+
fn parse_regex<'a>(
70+
allocator: &'a Allocator,
71+
pattern_span: Span,
72+
flags_span: Option<Span>,
73+
ctx: &'a LintContext<'_>,
74+
) -> Option<Pattern<'a>> {
75+
let flags_text = flags_span.map(|span| span.source_text(ctx.source_text()));
76+
let parser = ConstructorParser::new(
77+
allocator,
78+
pattern_span.source_text(ctx.source_text()),
79+
flags_text,
80+
Options {
81+
pattern_span_offset: pattern_span.start,
82+
flags_span_offset: flags_span.map_or(0, |span| span.start),
83+
},
84+
);
85+
let Ok(pattern) = parser.parse() else { return None };
86+
Some(pattern)
87+
}

0 commit comments

Comments
 (0)