UNPKG

4.32 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to forbid control charactes from regular expressions.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "disallow control characters in regular expressions",
16 category: "Possible Errors",
17 recommended: true,
18 url: "https://eslint.org/docs/rules/no-control-regex"
19 },
20
21 schema: [],
22
23 messages: {
24 unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}."
25 }
26 },
27
28 create(context) {
29
30 /**
31 * Get the regex expression
32 * @param {ASTNode} node node to evaluate
33 * @returns {RegExp|null} Regex if found else null
34 * @private
35 */
36 function getRegExp(node) {
37 if (node.value instanceof RegExp) {
38 return node.value;
39 }
40 if (typeof node.value === "string") {
41
42 const parent = context.getAncestors().pop();
43
44 if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
45 parent.callee.type === "Identifier" && parent.callee.name === "RegExp"
46 ) {
47
48 // there could be an invalid regular expression string
49 try {
50 return new RegExp(node.value);
51 } catch (ex) {
52 return null;
53 }
54 }
55 }
56
57 return null;
58 }
59
60
61 const controlChar = /[\x00-\x1f]/g; // eslint-disable-line no-control-regex
62 const consecutiveSlashes = /\\+/g;
63 const consecutiveSlashesAtEnd = /\\+$/g;
64 const stringControlChar = /\\x[01][0-9a-f]/ig;
65 const stringControlCharWithoutSlash = /x[01][0-9a-f]/ig;
66
67 /**
68 * Return a list of the control characters in the given regex string
69 * @param {string} regexStr regex as string to check
70 * @returns {array} returns a list of found control characters on given string
71 * @private
72 */
73 function getControlCharacters(regexStr) {
74
75 // check control characters, if RegExp object used
76 const controlChars = regexStr.match(controlChar) || [];
77
78 let stringControlChars = [];
79
80 // check substr, if regex literal used
81 const subStrIndex = regexStr.search(stringControlChar);
82
83 if (subStrIndex > -1) {
84
85 // is it escaped, check backslash count
86 const possibleEscapeCharacters = regexStr.slice(0, subStrIndex).match(consecutiveSlashesAtEnd);
87
88 const hasControlChars = possibleEscapeCharacters === null || !(possibleEscapeCharacters[0].length % 2);
89
90 if (hasControlChars) {
91 stringControlChars = regexStr.slice(subStrIndex, -1)
92 .split(consecutiveSlashes)
93 .filter(Boolean)
94 .map(x => {
95 const match = x.match(stringControlCharWithoutSlash) || [x];
96
97 return `\\${match[0]}`;
98 });
99 }
100 }
101
102 return controlChars.map(x => {
103 const hexCode = `0${x.charCodeAt(0).toString(16)}`.slice(-2);
104
105 return `\\x${hexCode}`;
106 }).concat(stringControlChars);
107 }
108
109 return {
110 Literal(node) {
111 const regex = getRegExp(node);
112
113 if (regex) {
114 const computedValue = regex.toString();
115
116 const controlCharacters = getControlCharacters(computedValue);
117
118 if (controlCharacters.length > 0) {
119 context.report({
120 node,
121 messageId: "unexpected",
122 data: {
123 controlChars: controlCharacters.join(", ")
124 }
125 });
126 }
127 }
128 }
129 };
130
131 }
132};