2 * @fileoverview Rule to flag non-camelcased identifiers
3 * @author Nicholas C. Zakas
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
17 description: "enforce camelcase naming convention",
18 category: "Stylistic Issues",
20 url: "https://eslint.org/docs/rules/camelcase"
27 ignoreDestructuring: {
40 enum: ["always", "never"]
53 additionalProperties: false
58 notCamelCase: "Identifier '{{name}}' is not in camel case."
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 || [];
73 if (properties !== "always" && properties !== "never") {
74 properties = "always";
77 //--------------------------------------------------------------------------
79 //--------------------------------------------------------------------------
81 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
83 const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
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
91 function isUnderscored(name) {
93 // if there's an underscore, it might be A_CONSTANT, which is okay
94 return name.includes("_") && name !== name.toUpperCase();
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
103 function isAllowed(name) {
105 entry => name === entry || name.match(new RegExp(entry, "u"))
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
115 function isInsideObjectPattern(node) {
119 const parent = current.parent;
121 if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
125 if (current.type === "ObjectPattern") {
136 * Checks whether the given node represents assignment target property in destructuring.
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.
146 function isAssignmentTargetPropertyInDestructuring(node) {
148 node.parent.type === "MemberExpression" &&
149 node.parent.property === node &&
150 !node.parent.computed
152 const effectiveParent = node.parent.parent;
155 effectiveParent.type === "Property" &&
156 effectiveParent.value === node.parent &&
157 effectiveParent.parent.type === "ObjectPattern" ||
158 effectiveParent.type === "ArrayPattern" ||
159 effectiveParent.type === "RestElement" ||
161 effectiveParent.type === "AssignmentPattern" &&
162 effectiveParent.left === node.parent
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.
175 function isReferenceToGlobalVariable(node) {
176 const variable = globalScope.set.get(node.name);
178 return variable && variable.defs.length === 0 &&
179 variable.references.some(ref => ref.identifier === node);
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
189 function isPropertyNameInObjectLiteral(node) {
190 const parent = node.parent;
193 parent.type === "Property" &&
194 parent.parent.type === "ObjectExpression" &&
201 * Reports an AST node as a rule violation.
202 * @param {ASTNode} node The node to report.
206 function report(node) {
207 if (!reported.includes(node)) {
209 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
216 globalScope = context.getScope();
222 * Leading and trailing underscores are commonly used to flag
223 * private/protected identifiers, strip them before checking if underscored
225 const name = node.name,
226 nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
227 effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
229 // First, we ignore the node if it match the ignore list
230 if (isAllowed(name)) {
234 // Check if it's a global variable
235 if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
239 // MemberExpressions get special rules
240 if (node.parent.type === "MemberExpression") {
242 // "never" check properties
243 if (properties === "never") {
247 // Always report underscored object names
248 if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
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)) {
255 } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
260 * Properties have their own rules, and
261 * AssignmentPattern nodes can be treated like Properties:
262 * e.g.: const { no_camelcased = false } = bar;
264 } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
266 if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
267 if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
271 const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
273 if (nameIsUnderscored && node.parent.computed) {
277 // prevent checking righthand side of destructured object
278 if (node.parent.key === node && node.parent.value !== node) {
282 const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
284 // ignore destructuring if the option is set, unless a new identifier is created
285 if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
290 // "never" check properties or always ignore destructuring
291 if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
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)) {
300 // Check if it's an import specifier
301 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
303 if (node.parent.type === "ImportSpecifier" && ignoreImports) {
307 // Report only if the local imported identifier is underscored
310 node.parent.local.name === node.name &&
316 // Report anything that is underscored that isn't a CallExpression
317 } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {