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