1/** 2 * @fileoverview Rule to forbid control characters from regular expressions. 3 * @author Nicholas C. Zakas 4 */ 5 6"use strict"; 7 8const RegExpValidator = require("regexpp").RegExpValidator; 9const collector = new (class { 10 constructor() { 11 this.ecmaVersion = 2018; 12 this._source = ""; 13 this._controlChars = []; 14 this._validator = new RegExpValidator(this); 15 } 16 17 onPatternEnter() { 18 this._controlChars = []; 19 } 20 21 onCharacter(start, end, cp) { 22 if (cp >= 0x00 && 23 cp <= 0x1F && 24 ( 25 this._source.codePointAt(start) === cp || 26 this._source.slice(start, end).startsWith("\\x") || 27 this._source.slice(start, end).startsWith("\\u") 28 ) 29 ) { 30 this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`); 31 } 32 } 33 34 collectControlChars(regexpStr) { 35 try { 36 this._source = regexpStr; 37 this._validator.validatePattern(regexpStr); // Call onCharacter hook 38 } catch { 39 40 // Ignore syntax errors in RegExp. 41 } 42 return this._controlChars; 43 } 44})(); 45 46//------------------------------------------------------------------------------ 47// Rule Definition 48//------------------------------------------------------------------------------ 49 50module.exports = { 51 meta: { 52 type: "problem", 53 54 docs: { 55 description: "disallow control characters in regular expressions", 56 category: "Possible Errors", 57 recommended: true, 58 url: "https://eslint.org/docs/rules/no-control-regex" 59 }, 60 61 schema: [], 62 63 messages: { 64 unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}." 65 } 66 }, 67 68 create(context) { 69 70 /** 71 * Get the regex expression 72 * @param {ASTNode} node node to evaluate 73 * @returns {RegExp|null} Regex if found else null 74 * @private 75 */ 76 function getRegExpPattern(node) { 77 if (node.regex) { 78 return node.regex.pattern; 79 } 80 if (typeof node.value === "string" && 81 (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") && 82 node.parent.callee.type === "Identifier" && 83 node.parent.callee.name === "RegExp" && 84 node.parent.arguments[0] === node 85 ) { 86 return node.value; 87 } 88 89 return null; 90 } 91 92 return { 93 Literal(node) { 94 const pattern = getRegExpPattern(node); 95 96 if (pattern) { 97 const controlCharacters = collector.collectControlChars(pattern); 98 99 if (controlCharacters.length > 0) { 100 context.report({ 101 node, 102 messageId: "unexpected", 103 data: { 104 controlChars: controlCharacters.join(", ") 105 } 106 }); 107 } 108 } 109 } 110 }; 111 112 } 113}; 114