.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / camelcase.js
1 /**
2  * @fileoverview Rule to flag non-camelcased identifiers
3  * @author Nicholas C. Zakas
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: "enforce camelcase naming convention",
18             category: "Stylistic Issues",
19             recommended: false,
20             url: "https://eslint.org/docs/rules/camelcase"
21         },
22
23         schema: [
24             {
25                 type: "object",
26                 properties: {
27                     ignoreDestructuring: {
28                         type: "boolean",
29                         default: false
30                     },
31                     ignoreImports: {
32                         type: "boolean",
33                         default: false
34                     },
35                     ignoreGlobals: {
36                         type: "boolean",
37                         default: false
38                     },
39                     properties: {
40                         enum: ["always", "never"]
41                     },
42                     allow: {
43                         type: "array",
44                         items: [
45                             {
46                                 type: "string"
47                             }
48                         ],
49                         minItems: 0,
50                         uniqueItems: true
51                     }
52                 },
53                 additionalProperties: false
54             }
55         ],
56
57         messages: {
58             notCamelCase: "Identifier '{{name}}' is not in camel case."
59         }
60     },
61
62     create(context) {
63
64         const options = context.options[0] || {};
65         let properties = options.properties || "";
66         const ignoreDestructuring = options.ignoreDestructuring;
67         const ignoreImports = options.ignoreImports;
68         const ignoreGlobals = options.ignoreGlobals;
69         const allow = options.allow || [];
70
71         let globalScope;
72
73         if (properties !== "always" && properties !== "never") {
74             properties = "always";
75         }
76
77         //--------------------------------------------------------------------------
78         // Helpers
79         //--------------------------------------------------------------------------
80
81         // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
82         const reported = [];
83         const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
84
85         /**
86          * Checks if a string contains an underscore and isn't all upper-case
87          * @param {string} name The string to check.
88          * @returns {boolean} if the string is underscored
89          * @private
90          */
91         function isUnderscored(name) {
92
93             // if there's an underscore, it might be A_CONSTANT, which is okay
94             return name.includes("_") && name !== name.toUpperCase();
95         }
96
97         /**
98          * Checks if a string match the ignore list
99          * @param {string} name The string to check.
100          * @returns {boolean} if the string is ignored
101          * @private
102          */
103         function isAllowed(name) {
104             return allow.some(
105                 entry => name === entry || name.match(new RegExp(entry, "u"))
106             );
107         }
108
109         /**
110          * Checks if a parent of a node is an ObjectPattern.
111          * @param {ASTNode} node The node to check.
112          * @returns {boolean} if the node is inside an ObjectPattern
113          * @private
114          */
115         function isInsideObjectPattern(node) {
116             let current = node;
117
118             while (current) {
119                 const parent = current.parent;
120
121                 if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
122                     return false;
123                 }
124
125                 if (current.type === "ObjectPattern") {
126                     return true;
127                 }
128
129                 current = parent;
130             }
131
132             return false;
133         }
134
135         /**
136          * Checks whether the given node represents assignment target property in destructuring.
137          *
138          * For examples:
139          *    ({a: b.foo} = c);  // => true for `foo`
140          *    ([a.foo] = b);     // => true for `foo`
141          *    ([a.foo = 1] = b); // => true for `foo`
142          *    ({...a.foo} = b);  // => true for `foo`
143          * @param {ASTNode} node An Identifier node to check
144          * @returns {boolean} True if the node is an assignment target property in destructuring.
145          */
146         function isAssignmentTargetPropertyInDestructuring(node) {
147             if (
148                 node.parent.type === "MemberExpression" &&
149                 node.parent.property === node &&
150                 !node.parent.computed
151             ) {
152                 const effectiveParent = node.parent.parent;
153
154                 return (
155                     effectiveParent.type === "Property" &&
156                     effectiveParent.value === node.parent &&
157                     effectiveParent.parent.type === "ObjectPattern" ||
158                     effectiveParent.type === "ArrayPattern" ||
159                     effectiveParent.type === "RestElement" ||
160                     (
161                         effectiveParent.type === "AssignmentPattern" &&
162                         effectiveParent.left === node.parent
163                     )
164                 );
165             }
166             return false;
167         }
168
169         /**
170          * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
171          * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
172          * @param {ASTNode} node `Identifier` node to check.
173          * @returns {boolean} `true` if the node is a reference to a global variable.
174          */
175         function isReferenceToGlobalVariable(node) {
176             const variable = globalScope.set.get(node.name);
177
178             return variable && variable.defs.length === 0 &&
179                 variable.references.some(ref => ref.identifier === node);
180         }
181
182         /**
183          * Checks whether the given node represents a reference to a property of an object in an object literal expression.
184          * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
185          * of the expressed object (which shouldn't be allowed).
186          * @param {ASTNode} node `Identifier` node to check.
187          * @returns {boolean} `true` if the node is a property name of an object literal expression
188          */
189         function isPropertyNameInObjectLiteral(node) {
190             const parent = node.parent;
191
192             return (
193                 parent.type === "Property" &&
194                 parent.parent.type === "ObjectExpression" &&
195                 !parent.computed &&
196                 parent.key === node
197             );
198         }
199
200         /**
201          * Reports an AST node as a rule violation.
202          * @param {ASTNode} node The node to report.
203          * @returns {void}
204          * @private
205          */
206         function report(node) {
207             if (!reported.includes(node)) {
208                 reported.push(node);
209                 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
210             }
211         }
212
213         return {
214
215             Program() {
216                 globalScope = context.getScope();
217             },
218
219             Identifier(node) {
220
221                 /*
222                  * Leading and trailing underscores are commonly used to flag
223                  * private/protected identifiers, strip them before checking if underscored
224                  */
225                 const name = node.name,
226                     nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
227                     effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
228
229                 // First, we ignore the node if it match the ignore list
230                 if (isAllowed(name)) {
231                     return;
232                 }
233
234                 // Check if it's a global variable
235                 if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
236                     return;
237                 }
238
239                 // MemberExpressions get special rules
240                 if (node.parent.type === "MemberExpression") {
241
242                     // "never" check properties
243                     if (properties === "never") {
244                         return;
245                     }
246
247                     // Always report underscored object names
248                     if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
249                         report(node);
250
251                     // Report AssignmentExpressions only if they are the left side of the assignment
252                     } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
253                         report(node);
254
255                     } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
256                         report(node);
257                     }
258
259                 /*
260                  * Properties have their own rules, and
261                  * AssignmentPattern nodes can be treated like Properties:
262                  * e.g.: const { no_camelcased = false } = bar;
263                  */
264                 } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
265
266                     if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
267                         if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
268                             report(node);
269                         }
270
271                         const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
272
273                         if (nameIsUnderscored && node.parent.computed) {
274                             report(node);
275                         }
276
277                         // prevent checking righthand side of destructured object
278                         if (node.parent.key === node && node.parent.value !== node) {
279                             return;
280                         }
281
282                         const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
283
284                         // ignore destructuring if the option is set, unless a new identifier is created
285                         if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
286                             report(node);
287                         }
288                     }
289
290                     // "never" check properties or always ignore destructuring
291                     if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
292                         return;
293                     }
294
295                     // don't check right hand side of AssignmentExpression to prevent duplicate warnings
296                     if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
297                         report(node);
298                     }
299
300                 // Check if it's an import specifier
301                 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
302
303                     if (node.parent.type === "ImportSpecifier" && ignoreImports) {
304                         return;
305                     }
306
307                     // Report only if the local imported identifier is underscored
308                     if (
309                         node.parent.local &&
310                         node.parent.local.name === node.name &&
311                         nameIsUnderscored
312                     ) {
313                         report(node);
314                     }
315
316                 // Report anything that is underscored that isn't a CallExpression
317                 } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
318                     report(node);
319                 }
320             }
321
322         };
323
324     }
325 };