]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/id-match.js
7a6cd058f2ec41948e5b2fb833e156bbefa89f47
[pve-eslint.git] / eslint / lib / rules / id-match.js
1 /**
2 * @fileoverview Rule to flag non-matching identifiers
3 * @author Matthieu Larcher
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = {
13 meta: {
14 type: "suggestion",
15
16 docs: {
17 description: "require identifiers to match a specified regular expression",
18 recommended: false,
19 url: "https://eslint.org/docs/rules/id-match"
20 },
21
22 schema: [
23 {
24 type: "string"
25 },
26 {
27 type: "object",
28 properties: {
29 properties: {
30 type: "boolean",
31 default: false
32 },
33 classFields: {
34 type: "boolean",
35 default: false
36 },
37 onlyDeclarations: {
38 type: "boolean",
39 default: false
40 },
41 ignoreDestructuring: {
42 type: "boolean",
43 default: false
44 }
45 },
46 additionalProperties: false
47 }
48 ],
49 messages: {
50 notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
51 notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'."
52 }
53 },
54
55 create(context) {
56
57 //--------------------------------------------------------------------------
58 // Options
59 //--------------------------------------------------------------------------
60 const pattern = context.options[0] || "^.+$",
61 regexp = new RegExp(pattern, "u");
62
63 const options = context.options[1] || {},
64 checkProperties = !!options.properties,
65 checkClassFields = !!options.classFields,
66 onlyDeclarations = !!options.onlyDeclarations,
67 ignoreDestructuring = !!options.ignoreDestructuring;
68
69 //--------------------------------------------------------------------------
70 // Helpers
71 //--------------------------------------------------------------------------
72
73 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
74 const reportedNodes = new Set();
75 const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
76 const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
77 const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
78
79 /**
80 * Checks if a string matches the provided pattern
81 * @param {string} name The string to check.
82 * @returns {boolean} if the string is a match
83 * @private
84 */
85 function isInvalid(name) {
86 return !regexp.test(name);
87 }
88
89 /**
90 * Checks if a parent of a node is an ObjectPattern.
91 * @param {ASTNode} node The node to check.
92 * @returns {boolean} if the node is inside an ObjectPattern
93 * @private
94 */
95 function isInsideObjectPattern(node) {
96 let { parent } = node;
97
98 while (parent) {
99 if (parent.type === "ObjectPattern") {
100 return true;
101 }
102
103 parent = parent.parent;
104 }
105
106 return false;
107 }
108
109 /**
110 * Verifies if we should report an error or not based on the effective
111 * parent node and the identifier name.
112 * @param {ASTNode} effectiveParent The effective parent node of the node to be reported
113 * @param {string} name The identifier name of the identifier node
114 * @returns {boolean} whether an error should be reported or not
115 */
116 function shouldReport(effectiveParent, name) {
117 return (!onlyDeclarations || DECLARATION_TYPES.has(effectiveParent.type)) &&
118 !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && isInvalid(name);
119 }
120
121 /**
122 * Reports an AST node as a rule violation.
123 * @param {ASTNode} node The node to report.
124 * @returns {void}
125 * @private
126 */
127 function report(node) {
128
129 /*
130 * We used the range instead of the node because it's possible
131 * for the same identifier to be represented by two different
132 * nodes, with the most clear example being shorthand properties:
133 * { foo }
134 * In this case, "foo" is represented by one node for the name
135 * and one for the value. The only way to know they are the same
136 * is to look at the range.
137 */
138 if (!reportedNodes.has(node.range.toString())) {
139
140 const messageId = (node.type === "PrivateIdentifier")
141 ? "notMatchPrivate" : "notMatch";
142
143 context.report({
144 node,
145 messageId,
146 data: {
147 name: node.name,
148 pattern
149 }
150 });
151 reportedNodes.add(node.range.toString());
152 }
153 }
154
155 return {
156
157 Identifier(node) {
158 const name = node.name,
159 parent = node.parent,
160 effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
161
162 if (parent.type === "MemberExpression") {
163
164 if (!checkProperties) {
165 return;
166 }
167
168 // Always check object names
169 if (parent.object.type === "Identifier" &&
170 parent.object.name === name) {
171 if (isInvalid(name)) {
172 report(node);
173 }
174
175 // Report AssignmentExpressions left side's assigned variable id
176 } else if (effectiveParent.type === "AssignmentExpression" &&
177 effectiveParent.left.type === "MemberExpression" &&
178 effectiveParent.left.property.name === node.name) {
179 if (isInvalid(name)) {
180 report(node);
181 }
182
183 // Report AssignmentExpressions only if they are the left side of the assignment
184 } else if (effectiveParent.type === "AssignmentExpression" && effectiveParent.right.type !== "MemberExpression") {
185 if (isInvalid(name)) {
186 report(node);
187 }
188 }
189
190 /*
191 * Properties have their own rules, and
192 * AssignmentPattern nodes can be treated like Properties:
193 * e.g.: const { no_camelcased = false } = bar;
194 */
195 } else if (parent.type === "Property" || parent.type === "AssignmentPattern") {
196
197 if (parent.parent && parent.parent.type === "ObjectPattern") {
198 if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) {
199 report(node);
200 }
201
202 const assignmentKeyEqualsValue = parent.key.name === parent.value.name;
203
204 // prevent checking righthand side of destructured object
205 if (!assignmentKeyEqualsValue && parent.key === node) {
206 return;
207 }
208
209 const valueIsInvalid = parent.value.name && isInvalid(name);
210
211 // ignore destructuring if the option is set, unless a new identifier is created
212 if (valueIsInvalid && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
213 report(node);
214 }
215 }
216
217 // never check properties or always ignore destructuring
218 if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) {
219 return;
220 }
221
222 // don't check right hand side of AssignmentExpression to prevent duplicate warnings
223 if (parent.right !== node && shouldReport(effectiveParent, name)) {
224 report(node);
225 }
226
227 // Check if it's an import specifier
228 } else if (IMPORT_TYPES.has(parent.type)) {
229
230 // Report only if the local imported identifier is invalid
231 if (parent.local && parent.local.name === node.name && isInvalid(name)) {
232 report(node);
233 }
234
235 } else if (parent.type === "PropertyDefinition") {
236
237 if (checkClassFields && isInvalid(name)) {
238 report(node);
239 }
240
241 // Report anything that is invalid that isn't a CallExpression
242 } else if (shouldReport(effectiveParent, name)) {
243 report(node);
244 }
245 },
246
247 "PrivateIdentifier"(node) {
248
249 const isClassField = node.parent.type === "PropertyDefinition";
250
251 if (isClassField && !checkClassFields) {
252 return;
253 }
254
255 if (isInvalid(node.name)) {
256 report(node);
257 }
258 }
259
260 };
261
262 }
263 };