24e6b197be0b446cfd15b1420158505f025e9ffa
[dotfiles/.git] / no-control-regex.js
1 /**
2  * @fileoverview Rule to forbid control charactes from regular expressions.
3  * @author Nicholas C. Zakas
4  */
5
6 "use strict";
7
8 const RegExpValidator = require("regexpp").RegExpValidator;
9 const 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 (err) {
39
40             // Ignore syntax errors in RegExp.
41         }
42         return this._controlChars;
43     }
44 })();
45
46 //------------------------------------------------------------------------------
47 // Rule Definition
48 //------------------------------------------------------------------------------
49
50 module.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 };