1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Rules which relate to regular expressions.

use crate::group;
use once_cell::sync::Lazy;
use rslint_errors::Span;
use rslint_lexer::SyntaxKind;
use rslint_parser::{
    ast::{ArgList, Literal, LiteralKind},
    AstNode, SyntaxNode, SyntaxNodeExt,
};
use rslint_regex::{validate_flags, EcmaVersion, Flags, Parser, Regex};
use std::sync::Mutex;
use std::{collections::HashMap, ops::Range};

type RegexResult = Result<(Regex, Range<usize>), (Range<usize>, String)>;

pub(crate) static REGEX_MAP: Lazy<Mutex<HashMap<Range<usize>, RegexResult>>> =
    Lazy::new(|| Mutex::new(HashMap::new()));

group! {
    /// Rules which relate to regular expressions.
    regex,
    no_invalid_regexp::NoInvalidRegexp,
    simplify_regex::SimplifyRegex
}

pub(crate) fn maybe_parse_and_store_regex(
    node: &SyntaxNode,
    file_id: usize,
) -> Option<RegexResult> {
    let mut map_handle = REGEX_MAP.lock().unwrap();
    if let Some(r) = map_handle.get(&node.as_range()) {
        return Some(r.to_owned());
    }
    let r = collect_regex_from_node(node, file_id)?;
    map_handle.insert(node.as_range(), r.clone());
    Some(r)
}

fn collect_regex_from_node(node: &SyntaxNode, file_id: usize) -> Option<RegexResult> {
    match node.kind() {
        SyntaxKind::NEW_EXPR | SyntaxKind::CALL_EXPR => {
            let name = node.child_with_kind(SyntaxKind::NAME_REF);
            if name.map_or(false, |x| x.text() == "RegExp") {
                let mut args = node
                    .child_with_ast::<ArgList>()
                    .map(|x| x.args())
                    .into_iter()
                    .flatten();
                let pat = args.next().and_then(|x| {
                    Some((
                        x.syntax().try_to::<Literal>()?.inner_string_text()?,
                        x.range(),
                    ))
                });

                let flags = args.next().and_then(|x| {
                    Some((
                        x.syntax().try_to::<Literal>()?.inner_string_text()?,
                        x.range(),
                    ))
                });

                if let Some((pat, range)) = pat {
                    let range = range.as_range();
                    let new_range = range.start + 1..range.end - 1;
                    let flags = if let Some((flags, flag_range)) = flags {
                        match validate_flags(&flags.to_string(), EcmaVersion::ES2021) {
                            Ok(f) => f,
                            Err(err) => {
                                return Some(Err((flag_range.as_range(), err)));
                            }
                        }
                    } else {
                        Flags::empty()
                    };

                    let pattern = &pat.to_string();
                    let parser = Parser::new_from_pattern_and_flags(
                        pattern,
                        file_id,
                        range.as_range().start + 1,
                        EcmaVersion::ES2021,
                        false,
                        flags,
                    );
                    Some(match parser.parse() {
                        Ok(r) => Ok((r, new_range)),
                        Err(err) => Err((err.span.as_range(), err.message)),
                    })
                } else {
                    None
                }
            } else {
                None
            }
        }
        SyntaxKind::LITERAL if node.to::<Literal>().kind() == LiteralKind::Regex => {
            let pattern = &node.text().to_string();
            let parser = Parser::new(
                pattern,
                file_id,
                node.as_range().start,
                EcmaVersion::ES2021,
                false,
            );
            let range = node.as_range();
            let new_range = range.start + 1..range.end - 1;
            let res = match parser {
                Ok(p) => p.parse(),
                Err(err) => {
                    return Some(Err((err.span.as_range(), err.message)));
                }
            };
            Some(match res {
                Ok(r) => Ok((r, new_range)),
                Err(err) => Err((err.span.as_range(), err.message)),
            })
        }
        _ => None,
    }
}