fix(linter/no-control-regex): better diagnostic messages (#6530)

1. Handle plural/singular cases in message and help text
2. Shrink spans in `RegExp` constructors to only cover the regular expression itself.
This commit is contained in:
DonIsaac 2024-10-13 19:34:19 +00:00
parent 6d5a9f2ee0
commit 685a590030
3 changed files with 31 additions and 15 deletions

View file

@ -12,10 +12,15 @@ use oxc_span::{GetSpan, Span};
use crate::{ast_util::extract_regex_flags, context::LintContext, rule::Rule, AstNode}; use crate::{ast_util::extract_regex_flags, context::LintContext, rule::Rule, AstNode};
fn no_control_regex_diagnostic(regex: &str, span: Span) -> OxcDiagnostic { fn no_control_regex_diagnostic(count: usize, regex: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unexpected control character(s)") debug_assert!(count > 0);
.with_help(format!("Unexpected control character(s) in regular expression: \"{regex}\"")) let (message, help) = if count == 1 {
.with_label(span) ("Unexpected control character", format!("'{regex}' is not a valid control character."))
} else {
("Unexpected control characters", format!("'{regex}' are not valid control characters."))
};
OxcDiagnostic::warn(message).with_help(help).with_label(span)
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -89,7 +94,12 @@ impl Rule for NoControlRegex {
if let Argument::StringLiteral(pattern) = &expr.arguments[0] { if let Argument::StringLiteral(pattern) = &expr.arguments[0] {
// get pattern from arguments. Missing or non-string arguments // get pattern from arguments. Missing or non-string arguments
// will be runtime errors, but are not covered by this rule. // will be runtime errors, but are not covered by this rule.
parse_and_check_regex(context, &pattern.value, &expr.arguments, expr.span); parse_and_check_regex(
context,
&pattern.value,
&expr.arguments,
pattern.span,
);
} }
} }
} }
@ -107,7 +117,12 @@ impl Rule for NoControlRegex {
if let Argument::StringLiteral(pattern) = &expr.arguments[0] { if let Argument::StringLiteral(pattern) = &expr.arguments[0] {
// get pattern from arguments. Missing or non-string arguments // get pattern from arguments. Missing or non-string arguments
// will be runtime errors, but are not covered by this rule. // will be runtime errors, but are not covered by this rule.
parse_and_check_regex(context, &pattern.value, &expr.arguments, expr.span); parse_and_check_regex(
context,
&pattern.value,
&expr.arguments,
pattern.span,
);
} }
} }
} }
@ -143,8 +158,9 @@ fn check_pattern(context: &LintContext, pattern: &Pattern, span: Span) {
finder.visit_pattern(pattern); finder.visit_pattern(pattern);
if !finder.control_chars.is_empty() { if !finder.control_chars.is_empty() {
let num_control_chars = finder.control_chars.len();
let violations = finder.control_chars.into_iter().map(|c| c.to_string()).join(", "); let violations = finder.control_chars.into_iter().map(|c| c.to_string()).join(", ");
context.diagnostic(no_control_regex_diagnostic(&violations, span)); context.diagnostic(no_control_regex_diagnostic(num_control_chars, &violations, span));
} }
} }

View file

@ -1,30 +1,30 @@
--- ---
source: crates/oxc_linter/src/tester.rs source: crates/oxc_linter/src/tester.rs
--- ---
⚠ eslint(no-control-regex): Unexpected control character(s) ⚠ eslint(no-control-regex): Unexpected control character
╭─[no_control_regex.tsx:1:11] ╭─[no_control_regex.tsx:1:11]
1 │ const r = /\0/; 1 │ const r = /\0/;
· ──── · ────
╰──── ╰────
help: Unexpected control character(s) in regular expression: "\0" help: '\0' is not a valid control character.
⚠ eslint(no-control-regex): Unexpected control character(s) ⚠ eslint(no-control-regex): Unexpected control character
╭─[no_control_regex.tsx:1:11] ╭─[no_control_regex.tsx:1:11]
1 │ const r = /[a-z]\1/; 1 │ const r = /[a-z]\1/;
· ───────── · ─────────
╰──── ╰────
help: Unexpected control character(s) in regular expression: "\1" help: '\1' is not a valid control character.
⚠ eslint(no-control-regex): Unexpected control character(s) ⚠ eslint(no-control-regex): Unexpected control character
╭─[no_control_regex.tsx:1:11] ╭─[no_control_regex.tsx:1:11]
1 │ const r = /([a-z])\2/; 1 │ const r = /([a-z])\2/;
· ─────────── · ───────────
╰──── ╰────
help: Unexpected control character(s) in regular expression: "\2" help: '\2' is not a valid control character.
⚠ eslint(no-control-regex): Unexpected control character(s) ⚠ eslint(no-control-regex): Unexpected control character
╭─[no_control_regex.tsx:1:11] ╭─[no_control_regex.tsx:1:11]
1 │ const r = /([a-z])\0/; 1 │ const r = /([a-z])\0/;
· ─────────── · ───────────
╰──── ╰────
help: Unexpected control character(s) in regular expression: "\0" help: '\0' is not a valid control character.