]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to forbid control characters 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 | }; |