UNPKG

10.5 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag dangling underscores in variable declarations.
3 * @author Matt DuVall <http://www.mattduvall.com>
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 type: "suggestion",
15
16 docs: {
17 description: "disallow dangling underscores in identifiers",
18 category: "Stylistic Issues",
19 recommended: false,
20 url: "https://eslint.org/docs/rules/no-underscore-dangle"
21 },
22
23 schema: [
24 {
25 type: "object",
26 properties: {
27 allow: {
28 type: "array",
29 items: {
30 type: "string"
31 }
32 },
33 allowAfterThis: {
34 type: "boolean",
35 default: false
36 },
37 allowAfterSuper: {
38 type: "boolean",
39 default: false
40 },
41 allowAfterThisConstructor: {
42 type: "boolean",
43 default: false
44 },
45 enforceInMethodNames: {
46 type: "boolean",
47 default: false
48 },
49 allowFunctionParams: {
50 type: "boolean",
51 default: true
52 }
53 },
54 additionalProperties: false
55 }
56 ],
57
58 messages: {
59 unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'."
60 }
61 },
62
63 create(context) {
64
65 const options = context.options[0] || {};
66 const ALLOWED_VARIABLES = options.allow ? options.allow : [];
67 const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
68 const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
69 const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
70 const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
71 const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
72
73 //-------------------------------------------------------------------------
74 // Helpers
75 //-------------------------------------------------------------------------
76
77 /**
78 * Check if identifier is present inside the allowed option
79 * @param {string} identifier name of the node
80 * @returns {boolean} true if its is present
81 * @private
82 */
83 function isAllowed(identifier) {
84 return ALLOWED_VARIABLES.some(ident => ident === identifier);
85 }
86
87 /**
88 * Check if identifier has a dangling underscore
89 * @param {string} identifier name of the node
90 * @returns {boolean} true if its is present
91 * @private
92 */
93 function hasDanglingUnderscore(identifier) {
94 const len = identifier.length;
95
96 return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
97 }
98
99 /**
100 * Check if identifier is a special case member expression
101 * @param {string} identifier name of the node
102 * @returns {boolean} true if its is a special case
103 * @private
104 */
105 function isSpecialCaseIdentifierForMemberExpression(identifier) {
106 return identifier === "__proto__";
107 }
108
109 /**
110 * Check if identifier is a special case variable expression
111 * @param {string} identifier name of the node
112 * @returns {boolean} true if its is a special case
113 * @private
114 */
115 function isSpecialCaseIdentifierInVariableExpression(identifier) {
116
117 // Checks for the underscore library usage here
118 return identifier === "_";
119 }
120
121 /**
122 * Check if a node is a member reference of this.constructor
123 * @param {ASTNode} node node to evaluate
124 * @returns {boolean} true if it is a reference on this.constructor
125 * @private
126 */
127 function isThisConstructorReference(node) {
128 return node.object.type === "MemberExpression" &&
129 node.object.property.name === "constructor" &&
130 node.object.object.type === "ThisExpression";
131 }
132
133 /**
134 * Check if function parameter has a dangling underscore.
135 * @param {ASTNode} node function node to evaluate
136 * @returns {void}
137 * @private
138 */
139 function checkForDanglingUnderscoreInFunctionParameters(node) {
140 if (!allowFunctionParams) {
141 node.params.forEach(param => {
142 const { type } = param;
143 let nodeToCheck;
144
145 if (type === "RestElement") {
146 nodeToCheck = param.argument;
147 } else if (type === "AssignmentPattern") {
148 nodeToCheck = param.left;
149 } else {
150 nodeToCheck = param;
151 }
152
153 if (nodeToCheck.type === "Identifier") {
154 const identifier = nodeToCheck.name;
155
156 if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
157 context.report({
158 node: param,
159 messageId: "unexpectedUnderscore",
160 data: {
161 identifier
162 }
163 });
164 }
165 }
166 });
167 }
168 }
169
170 /**
171 * Check if function has a dangling underscore
172 * @param {ASTNode} node node to evaluate
173 * @returns {void}
174 * @private
175 */
176 function checkForDanglingUnderscoreInFunction(node) {
177 if (node.type === "FunctionDeclaration" && node.id) {
178 const identifier = node.id.name;
179
180 if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
181 context.report({
182 node,
183 messageId: "unexpectedUnderscore",
184 data: {
185 identifier
186 }
187 });
188 }
189 }
190 checkForDanglingUnderscoreInFunctionParameters(node);
191 }
192
193 /**
194 * Check if variable expression has a dangling underscore
195 * @param {ASTNode} node node to evaluate
196 * @returns {void}
197 * @private
198 */
199 function checkForDanglingUnderscoreInVariableExpression(node) {
200 const identifier = node.id.name;
201
202 if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
203 !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
204 context.report({
205 node,
206 messageId: "unexpectedUnderscore",
207 data: {
208 identifier
209 }
210 });
211 }
212 }
213
214 /**
215 * Check if member expression has a dangling underscore
216 * @param {ASTNode} node node to evaluate
217 * @returns {void}
218 * @private
219 */
220 function checkForDanglingUnderscoreInMemberExpression(node) {
221 const identifier = node.property.name,
222 isMemberOfThis = node.object.type === "ThisExpression",
223 isMemberOfSuper = node.object.type === "Super",
224 isMemberOfThisConstructor = isThisConstructorReference(node);
225
226 if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
227 !(isMemberOfThis && allowAfterThis) &&
228 !(isMemberOfSuper && allowAfterSuper) &&
229 !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
230 !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
231 context.report({
232 node,
233 messageId: "unexpectedUnderscore",
234 data: {
235 identifier
236 }
237 });
238 }
239 }
240
241 /**
242 * Check if method declaration or method property has a dangling underscore
243 * @param {ASTNode} node node to evaluate
244 * @returns {void}
245 * @private
246 */
247 function checkForDanglingUnderscoreInMethod(node) {
248 const identifier = node.key.name;
249 const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
250
251 if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
252 context.report({
253 node,
254 messageId: "unexpectedUnderscore",
255 data: {
256 identifier
257 }
258 });
259 }
260 }
261
262 //--------------------------------------------------------------------------
263 // Public API
264 //--------------------------------------------------------------------------
265
266 return {
267 FunctionDeclaration: checkForDanglingUnderscoreInFunction,
268 VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
269 MemberExpression: checkForDanglingUnderscoreInMemberExpression,
270 MethodDefinition: checkForDanglingUnderscoreInMethod,
271 Property: checkForDanglingUnderscoreInMethod,
272 FunctionExpression: checkForDanglingUnderscoreInFunction,
273 ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
274 };
275
276 }
277};