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: {
36 enum: ["always", "never"]
49 additionalProperties: false
54 notCamelCase: "Identifier '{{name}}' is not in camel case."
60 const options = context.options[0] || {};
61 let properties = options.properties || "";
62 const ignoreDestructuring = options.ignoreDestructuring;
63 const ignoreImports = options.ignoreImports;
64 const allow = options.allow || [];
66 if (properties !== "always" && properties !== "never") {
67 properties = "always";
70 //--------------------------------------------------------------------------
72 //--------------------------------------------------------------------------
74 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
76 const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
79 * Checks if a string contains an underscore and isn't all upper-case
80 * @param {string} name The string to check.
81 * @returns {boolean} if the string is underscored
84 function isUnderscored(name) {
86 // if there's an underscore, it might be A_CONSTANT, which is okay
87 return name.includes("_") && name !== name.toUpperCase();
91 * Checks if a string match the ignore list
92 * @param {string} name The string to check.
93 * @returns {boolean} if the string is ignored
96 function isAllowed(name) {
98 entry => name === entry || name.match(new RegExp(entry, "u"))
103 * Checks if a parent of a node is an ObjectPattern.
104 * @param {ASTNode} node The node to check.
105 * @returns {boolean} if the node is inside an ObjectPattern
108 function isInsideObjectPattern(node) {
112 const parent = current.parent;
114 if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
118 if (current.type === "ObjectPattern") {
129 * Reports an AST node as a rule violation.
130 * @param {ASTNode} node The node to report.
134 function report(node) {
135 if (!reported.includes(node)) {
137 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
146 * Leading and trailing underscores are commonly used to flag
147 * private/protected identifiers, strip them before checking if underscored
149 const name = node.name,
150 nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
151 effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
153 // First, we ignore the node if it match the ignore list
154 if (isAllowed(name)) {
158 // MemberExpressions get special rules
159 if (node.parent.type === "MemberExpression") {
161 // "never" check properties
162 if (properties === "never") {
166 // Always report underscored object names
167 if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
170 // Report AssignmentExpressions only if they are the left side of the assignment
171 } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
176 * Properties have their own rules, and
177 * AssignmentPattern nodes can be treated like Properties:
178 * e.g.: const { no_camelcased = false } = bar;
180 } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
182 if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
183 if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
187 const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
189 if (isUnderscored(name) && node.parent.computed) {
193 // prevent checking righthand side of destructured object
194 if (node.parent.key === node && node.parent.value !== node) {
198 const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
200 // ignore destructuring if the option is set, unless a new identifier is created
201 if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
206 // "never" check properties or always ignore destructuring
207 if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
211 // don't check right hand side of AssignmentExpression to prevent duplicate warnings
212 if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
216 // Check if it's an import specifier
217 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
219 if (node.parent.type === "ImportSpecifier" && ignoreImports) {
223 // Report only if the local imported identifier is underscored
226 node.parent.local.name === node.name &&
232 // Report anything that is underscored that isn't a CallExpression
233 } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {