UNPKG

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