summaryrefslogtreecommitdiff
path: root/tools/eslint-rules/no-unescaped-regexp-dot.js
blob: e9349713bfa3fa7a1fd3059fa7a244f3483edfcd (plain)
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
123
124
125
126
127
128
129
130
/**
 * @fileoverview Look for unescaped "literal" dots in regular expressions
 * @author Brian White
 */
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {
  const sourceCode = context.getSourceCode();
  const regexpStack = [];
  let regexpBuffer = [];
  let inRegExp = false;

  function report(node, startOffset) {
    const indexOfDot = sourceCode.getIndexFromLoc(node.loc.start) + startOffset;
    context.report({
      node,
      loc: sourceCode.getLocFromIndex(indexOfDot),
      message: 'Unescaped dot character in regular expression'
    });
  }

  const allowedModifiers = ['+', '*', '?', '{'];
  function checkRegExp(nodes) {
    let escaping = false;
    let inCharClass = false;
    for (let n = 0; n < nodes.length; ++n) {
      const pair = nodes[n];
      const node = pair[0];
      const str = pair[1];
      for (let i = 0; i < str.length; ++i) {
        switch (str[i]) {
          case '[':
            if (!escaping)
              inCharClass = true;
            else
              escaping = false;
            break;
          case ']':
            if (!escaping) {
              if (inCharClass)
                inCharClass = false;
            } else {
              escaping = false;
            }
            break;
          case '\\':
            escaping = !escaping;
            break;
          case '.':
            if (!escaping) {
              if (!inCharClass &&
                  ((i + 1) === str.length ||
                   allowedModifiers.indexOf(str[i + 1]) === -1)) {
                report(node, i);
              }
            } else {
              escaping = false;
            }
            break;
          default:
            if (escaping)
              escaping = false;
        }
      }
    }
  }

  function checkRegExpStart(node) {
    if (node.callee && node.callee.name === 'RegExp') {
      if (inRegExp) {
        regexpStack.push(regexpBuffer);
        regexpBuffer = [];
      }
      inRegExp = true;
    }
  }

  function checkRegExpEnd(node) {
    if (node.callee && node.callee.name === 'RegExp') {
      checkRegExp(regexpBuffer);
      if (regexpStack.length) {
        regexpBuffer = regexpStack.pop();
      } else {
        inRegExp = false;
        regexpBuffer = [];
      }
    }
  }

  function checkLiteral(node) {
    const isTemplate = (node.type === 'TemplateLiteral' && node.quasis &&
                        node.quasis.length);
    if (inRegExp &&
        (isTemplate || (typeof node.value === 'string' && node.value.length))) {
      let p = node.parent;
      while (p && p.type === 'BinaryExpression') {
        p = p.parent;
      }
      if (p && (p.type === 'NewExpression' || p.type === 'CallExpression') &&
          p.callee && p.callee.type === 'Identifier' &&
          p.callee.name === 'RegExp') {
        if (isTemplate) {
          const quasis = node.quasis;
          for (let i = 0; i < quasis.length; ++i) {
            const el = quasis[i];
            if (el.type === 'TemplateElement' && el.value && el.value.cooked)
              regexpBuffer.push([el, el.value.cooked]);
          }
        } else {
          regexpBuffer.push([node, node.value]);
        }
      }
    } else if (node.regex) {
      checkRegExp([[node, node.regex.pattern]]);
    }
  }

  return {
    'TemplateLiteral': checkLiteral,
    'Literal': checkLiteral,
    'CallExpression': checkRegExpStart,
    'NewExpression': checkRegExpStart,
    'CallExpression:exit': checkRegExpEnd,
    'NewExpression:exit': checkRegExpEnd
  };
};