UNPKG

5.95 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 docs: {
15 description: "enforce camelcase naming convention",
16 category: "Stylistic Issues",
17 recommended: false,
18 url: "https://eslint.org/docs/rules/camelcase"
19 },
20
21 schema: [
22 {
23 type: "object",
24 properties: {
25 properties: {
26 enum: ["always", "never"]
27 }
28 },
29 additionalProperties: false
30 }
31 ],
32
33 messages: {
34 notCamelCase: "Identifier '{{name}}' is not in camel case."
35 }
36 },
37
38 create(context) {
39
40 //--------------------------------------------------------------------------
41 // Helpers
42 //--------------------------------------------------------------------------
43
44 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
45 const reported = [];
46 const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
47
48 /**
49 * Checks if a string contains an underscore and isn't all upper-case
50 * @param {string} name The string to check.
51 * @returns {boolean} if the string is underscored
52 * @private
53 */
54 function isUnderscored(name) {
55
56 // if there's an underscore, it might be A_CONSTANT, which is okay
57 return name.indexOf("_") > -1 && name !== name.toUpperCase();
58 }
59
60 /**
61 * Reports an AST node as a rule violation.
62 * @param {ASTNode} node The node to report.
63 * @returns {void}
64 * @private
65 */
66 function report(node) {
67 if (reported.indexOf(node) < 0) {
68 reported.push(node);
69 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
70 }
71 }
72
73 const options = context.options[0] || {};
74 let properties = options.properties || "";
75
76 if (properties !== "always" && properties !== "never") {
77 properties = "always";
78 }
79
80 return {
81
82 Identifier(node) {
83
84 /*
85 * Leading and trailing underscores are commonly used to flag
86 * private/protected identifiers, strip them
87 */
88 const name = node.name.replace(/^_+|_+$/g, ""),
89 effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
90
91 // MemberExpressions get special rules
92 if (node.parent.type === "MemberExpression") {
93
94 // "never" check properties
95 if (properties === "never") {
96 return;
97 }
98
99 // Always report underscored object names
100 if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && isUnderscored(name)) {
101 report(node);
102
103 // Report AssignmentExpressions only if they are the left side of the assignment
104 } else if (effectiveParent.type === "AssignmentExpression" && isUnderscored(name) && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
105 report(node);
106 }
107
108 /*
109 * Properties have their own rules, and
110 * AssignmentPattern nodes can be treated like Properties:
111 * e.g.: const { no_camelcased = false } = bar;
112 */
113 } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
114
115 if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
116
117 if (node.parent.shorthand && node.parent.value.left && isUnderscored(name)) {
118
119 report(node);
120 }
121
122 // prevent checking righthand side of destructured object
123 if (node.parent.key === node && node.parent.value !== node) {
124 return;
125 }
126
127 if (node.parent.value.name && isUnderscored(name)) {
128 report(node);
129 }
130 }
131
132 // "never" check properties
133 if (properties === "never") {
134 return;
135 }
136
137 // don't check right hand side of AssignmentExpression to prevent duplicate warnings
138 if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
139 report(node);
140 }
141
142 // Check if it's an import specifier
143 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
144
145 // Report only if the local imported identifier is underscored
146 if (node.parent.local && node.parent.local.name === node.name && isUnderscored(name)) {
147 report(node);
148 }
149
150 // Report anything that is underscored that isn't a CallExpression
151 } else if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
152 report(node);
153 }
154 }
155
156 };
157
158 }
159};