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