use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::{Program, ProgramRef};
use deno_ast::view::Regex;
use deno_ast::SourceRanged;
use once_cell::sync::Lazy;
use std::sync::Arc;
#[derive(Debug)]
pub struct NoEmptyCharacterClass;
const CODE: &str = "no-empty-character-class";
const MESSAGE: &str = "empty character class in RegExp is not allowed";
const HINT: &str =
"Remove or rework the empty character class (`[]`) in the RegExp";
impl LintRule for NoEmptyCharacterClass {
fn new() -> Arc<Self> {
Arc::new(NoEmptyCharacterClass)
}
fn tags(&self) -> &'static [&'static str] {
&["recommended"]
}
fn code(&self) -> &'static str {
CODE
}
fn lint_program(&self, _context: &mut Context, _program: ProgramRef) {
unreachable!();
}
fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program,
) {
NoEmptyCharacterClassVisitor.traverse(program, context);
}
#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/no_empty_character_class.md")
}
}
struct NoEmptyCharacterClassVisitor;
impl Handler for NoEmptyCharacterClassVisitor {
fn regex(&mut self, regex: &Regex, ctx: &mut Context) {
let raw_regex = regex.text_fast(&ctx.text_info());
static RULE_REGEX: Lazy<regex::Regex> = Lazy::new(|| {
regex::Regex::new(r"(?u)^/([^\\\[]|\\.|\[([^\\\]]|\\.)+\])*/[gimuysd]*$")
.unwrap()
});
if !RULE_REGEX.is_match(raw_regex) {
ctx.add_diagnostic_with_hint(regex.range(), CODE, MESSAGE, HINT);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_empty_character_class_valid() {
assert_lint_ok! {
NoEmptyCharacterClass,
r#"
const foo = /^abc[a-zA-Z]/;
const regExp = new RegExp("^abc[]");
const foo = /^abc/;
const foo = /[\\[]/;
const foo = /[\\]]/;
const foo = /[a-zA-Z\\[]/;
const foo = /[[]/;
const foo = /[\\[a-z[]]/;
const foo = /[\-\[\]\/\{\}\(\)\*\+\?\.\\^\$\|]/g;
const foo = /\[/g;
const foo = /\]/i;
const foo = /\]/d;
"#,
};
}
#[test]
fn no_empty_character_invalid() {
assert_lint_err! {
NoEmptyCharacterClass,
r#"const foo = /^abc[]/;"#: [{
col: 12,
message: MESSAGE,
hint: HINT,
}],
r#"const foo = /foo[]bar/;"#: [{
col: 12,
message: MESSAGE,
hint: HINT,
}],
r#"const foo = /[]]/;"#: [{
col: 12,
message: MESSAGE,
hint: HINT,
}],
r#"const foo = /\[[]/;"#: [{
col: 12,
message: MESSAGE,
hint: HINT,
}],
r#"const foo = /\\[\\[\\]a-z[]/;"#: [{
col: 12,
message: MESSAGE,
hint: HINT,
}],
r#"/^abc[]/.test("abcdefg");"#: [{
col: 0,
message: MESSAGE,
hint: HINT,
}],
r#"if (foo.match(/^abc[]/)) {}"#: [{
col: 14,
message: MESSAGE,
hint: HINT,
}],
r#""abcdefg".match(/^abc[]/);"#: [{
col: 16,
message: MESSAGE,
hint: HINT,
}],
r#"if (/^abc[]/.test(foo)) {}"#: [{
col: 4,
message: MESSAGE,
hint: HINT,
}],
}
}
}