• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Look for unescaped "literal" dots in regular expressions
3 * @author Brian White
4 */
5'use strict';
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = function(context) {
12  const sourceCode = context.getSourceCode();
13  const regexpStack = [];
14  let regexpBuffer = [];
15  let inRegExp = false;
16
17  function report(node, startOffset) {
18    const indexOfDot = sourceCode.getIndexFromLoc(node.loc.start) + startOffset;
19    context.report({
20      node,
21      loc: sourceCode.getLocFromIndex(indexOfDot),
22      message: 'Unescaped dot character in regular expression'
23    });
24  }
25
26  const allowedModifiers = ['+', '*', '?', '{'];
27  function checkRegExp(nodes) {
28    let escaping = false;
29    let inCharClass = false;
30    for (let n = 0; n < nodes.length; ++n) {
31      const pair = nodes[n];
32      const node = pair[0];
33      const str = pair[1];
34      for (let i = 0; i < str.length; ++i) {
35        switch (str[i]) {
36          case '[':
37            if (!escaping)
38              inCharClass = true;
39            else
40              escaping = false;
41            break;
42          case ']':
43            if (!escaping) {
44              if (inCharClass)
45                inCharClass = false;
46            } else {
47              escaping = false;
48            }
49            break;
50          case '\\':
51            escaping = !escaping;
52            break;
53          case '.':
54            if (!escaping) {
55              if (!inCharClass &&
56                  ((i + 1) === str.length ||
57                   allowedModifiers.indexOf(str[i + 1]) === -1)) {
58                report(node, i);
59              }
60            } else {
61              escaping = false;
62            }
63            break;
64          default:
65            if (escaping)
66              escaping = false;
67        }
68      }
69    }
70  }
71
72  function checkRegExpStart(node) {
73    if (node.callee && node.callee.name === 'RegExp') {
74      if (inRegExp) {
75        regexpStack.push(regexpBuffer);
76        regexpBuffer = [];
77      }
78      inRegExp = true;
79    }
80  }
81
82  function checkRegExpEnd(node) {
83    if (node.callee && node.callee.name === 'RegExp') {
84      checkRegExp(regexpBuffer);
85      if (regexpStack.length) {
86        regexpBuffer = regexpStack.pop();
87      } else {
88        inRegExp = false;
89        regexpBuffer = [];
90      }
91    }
92  }
93
94  function checkLiteral(node) {
95    const isTemplate = (node.type === 'TemplateLiteral' && node.quasis &&
96                        node.quasis.length);
97    if (inRegExp &&
98        (isTemplate || (typeof node.value === 'string' && node.value.length))) {
99      let p = node.parent;
100      while (p && p.type === 'BinaryExpression') {
101        p = p.parent;
102      }
103      if (p && (p.type === 'NewExpression' || p.type === 'CallExpression') &&
104          p.callee && p.callee.type === 'Identifier' &&
105          p.callee.name === 'RegExp') {
106        if (isTemplate) {
107          const quasis = node.quasis;
108          for (let i = 0; i < quasis.length; ++i) {
109            const el = quasis[i];
110            if (el.type === 'TemplateElement' && el.value && el.value.cooked)
111              regexpBuffer.push([el, el.value.cooked]);
112          }
113        } else {
114          regexpBuffer.push([node, node.value]);
115        }
116      }
117    } else if (node.regex) {
118      checkRegExp([[node, node.regex.pattern]]);
119    }
120  }
121
122  return {
123    'TemplateLiteral': checkLiteral,
124    'Literal': checkLiteral,
125    'CallExpression': checkRegExpStart,
126    'NewExpression': checkRegExpStart,
127    'CallExpression:exit': checkRegExpEnd,
128    'NewExpression:exit': checkRegExpEnd
129  };
130};
131