]>
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 | ||
f2a92ac6 | 8 | const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; |
eb39fafa DC |
9 | const collector = new (class { |
10 | constructor() { | |
eb39fafa DC |
11 | 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 | ||
8f9d1d4d DC |
33 | collectControlChars(regexpStr, flags) { |
34 | const uFlag = typeof flags === "string" && flags.includes("u"); | |
35 | ||
eb39fafa DC |
36 | try { |
37 | this._source = regexpStr; | |
8f9d1d4d | 38 | this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook |
d3726936 | 39 | } catch { |
eb39fafa DC |
40 | |
41 | // Ignore syntax errors in RegExp. | |
42 | } | |
43 | return this._controlChars; | |
44 | } | |
45 | })(); | |
46 | ||
47 | //------------------------------------------------------------------------------ | |
48 | // Rule Definition | |
49 | //------------------------------------------------------------------------------ | |
50 | ||
34eeec05 | 51 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
52 | module.exports = { |
53 | meta: { | |
54 | type: "problem", | |
55 | ||
56 | docs: { | |
8f9d1d4d | 57 | description: "Disallow control characters in regular expressions", |
eb39fafa | 58 | recommended: true, |
f2a92ac6 | 59 | url: "https://eslint.org/docs/latest/rules/no-control-regex" |
eb39fafa DC |
60 | }, |
61 | ||
62 | schema: [], | |
63 | ||
64 | messages: { | |
65 | unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}." | |
66 | } | |
67 | }, | |
68 | ||
69 | create(context) { | |
70 | ||
71 | /** | |
72 | * Get the regex expression | |
8f9d1d4d DC |
73 | * @param {ASTNode} node `Literal` node to evaluate |
74 | * @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal | |
75 | * or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined, | |
76 | * the `flags` property will be `null`. | |
eb39fafa DC |
77 | * @private |
78 | */ | |
8f9d1d4d | 79 | function getRegExp(node) { |
eb39fafa | 80 | if (node.regex) { |
8f9d1d4d | 81 | return node.regex; |
eb39fafa DC |
82 | } |
83 | if (typeof node.value === "string" && | |
84 | (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") && | |
85 | node.parent.callee.type === "Identifier" && | |
86 | node.parent.callee.name === "RegExp" && | |
87 | node.parent.arguments[0] === node | |
88 | ) { | |
8f9d1d4d DC |
89 | const pattern = node.value; |
90 | const flags = | |
91 | node.parent.arguments.length > 1 && | |
92 | node.parent.arguments[1].type === "Literal" && | |
93 | typeof node.parent.arguments[1].value === "string" | |
94 | ? node.parent.arguments[1].value | |
95 | : null; | |
96 | ||
97 | return { pattern, flags }; | |
eb39fafa DC |
98 | } |
99 | ||
100 | return null; | |
101 | } | |
102 | ||
103 | return { | |
104 | Literal(node) { | |
8f9d1d4d | 105 | const regExp = getRegExp(node); |
eb39fafa | 106 | |
8f9d1d4d DC |
107 | if (regExp) { |
108 | const { pattern, flags } = regExp; | |
109 | const controlCharacters = collector.collectControlChars(pattern, flags); | |
eb39fafa DC |
110 | |
111 | if (controlCharacters.length > 0) { | |
112 | context.report({ | |
113 | node, | |
114 | messageId: "unexpected", | |
115 | data: { | |
116 | controlChars: controlCharacters.join(", ") | |
117 | } | |
118 | }); | |
119 | } | |
120 | } | |
121 | } | |
122 | }; | |
123 | ||
124 | } | |
125 | }; |