UNPKG

8.49 kBJavaScriptView Raw
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
12module.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 properties: {
32 enum: ["always", "never"]
33 },
34 allow: {
35 type: "array",
36 items: [
37 {
38 type: "string"
39 }
40 ],
41 minItems: 0,
42 uniqueItems: true
43 }
44 },
45 additionalProperties: false
46 }
47 ],
48
49 messages: {
50 notCamelCase: "Identifier '{{name}}' is not in camel case."
51 }
52 },
53
54 create(context) {
55
56 const options = context.options[0] || {};
57 let properties = options.properties || "";
58 const ignoreDestructuring = options.ignoreDestructuring;
59 const allow = options.allow || [];
60
61 if (properties !== "always" && properties !== "never") {
62 properties = "always";
63 }
64
65 //--------------------------------------------------------------------------
66 // Helpers
67 //--------------------------------------------------------------------------
68
69 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
70 const reported = [];
71 const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
72
73 /**
74 * Checks if a string contains an underscore and isn't all upper-case
75 * @param {string} name The string to check.
76 * @returns {boolean} if the string is underscored
77 * @private
78 */
79 function isUnderscored(name) {
80
81 // if there's an underscore, it might be A_CONSTANT, which is okay
82 return name.indexOf("_") > -1 && name !== name.toUpperCase();
83 }
84
85 /**
86 * Checks if a string match the ignore list
87 * @param {string} name The string to check.
88 * @returns {boolean} if the string is ignored
89 * @private
90 */
91 function isAllowed(name) {
92 return allow.findIndex(
93 entry => name === entry || name.match(new RegExp(entry, "u"))
94 ) !== -1;
95 }
96
97 /**
98 * Checks if a parent of a node is an ObjectPattern.
99 * @param {ASTNode} node The node to check.
100 * @returns {boolean} if the node is inside an ObjectPattern
101 * @private
102 */
103 function isInsideObjectPattern(node) {
104 let current = node;
105
106 while (current) {
107 const parent = current.parent;
108
109 if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
110 return false;
111 }
112
113 if (current.type === "ObjectPattern") {
114 return true;
115 }
116
117 current = parent;
118 }
119
120 return false;
121 }
122
123 /**
124 * Reports an AST node as a rule violation.
125 * @param {ASTNode} node The node to report.
126 * @returns {void}
127 * @private
128 */
129 function report(node) {
130 if (reported.indexOf(node) < 0) {
131 reported.push(node);
132 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
133 }
134 }
135
136 return {
137
138 Identifier(node) {
139
140 /*
141 * Leading and trailing underscores are commonly used to flag
142 * private/protected identifiers, strip them before checking if underscored
143 */
144 const name = node.name,
145 nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
146 effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
147
148 // First, we ignore the node if it match the ignore list
149 if (isAllowed(name)) {
150 return;
151 }
152
153 // MemberExpressions get special rules
154 if (node.parent.type === "MemberExpression") {
155
156 // "never" check properties
157 if (properties === "never") {
158 return;
159 }
160
161 // Always report underscored object names
162 if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
163 report(node);
164
165 // Report AssignmentExpressions only if they are the left side of the assignment
166 } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
167 report(node);
168 }
169
170 /*
171 * Properties have their own rules, and
172 * AssignmentPattern nodes can be treated like Properties:
173 * e.g.: const { no_camelcased = false } = bar;
174 */
175 } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
176
177 if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
178 if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
179 report(node);
180 }
181
182 const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
183
184 if (isUnderscored(name) && node.parent.computed) {
185 report(node);
186 }
187
188 // prevent checking righthand side of destructured object
189 if (node.parent.key === node && node.parent.value !== node) {
190 return;
191 }
192
193 const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
194
195 // ignore destructuring if the option is set, unless a new identifier is created
196 if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
197 report(node);
198 }
199 }
200
201 // "never" check properties or always ignore destructuring
202 if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
203 return;
204 }
205
206 // don't check right hand side of AssignmentExpression to prevent duplicate warnings
207 if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
208 report(node);
209 }
210
211 // Check if it's an import specifier
212 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
213
214 // Report only if the local imported identifier is underscored
215 if (node.parent.local && node.parent.local.name === node.name && nameIsUnderscored) {
216 report(node);
217 }
218
219 // Report anything that is underscored that isn't a CallExpression
220 } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
221 report(node);
222 }
223 }
224
225 };
226
227 }
228};