| 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 |
|
|---|
| 12 | /** @type {import('../shared/types').Rule} */
|
|---|
| 13 | module.exports = {
|
|---|
| 14 | meta: {
|
|---|
| 15 | type: "suggestion",
|
|---|
| 16 |
|
|---|
| 17 | docs: {
|
|---|
| 18 | description: "Disallow dangling underscores in identifiers",
|
|---|
| 19 | recommended: false,
|
|---|
| 20 | url: "https://eslint.org/docs/latest/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 | enforceInClassFields: {
|
|---|
| 54 | type: "boolean",
|
|---|
| 55 | default: false
|
|---|
| 56 | },
|
|---|
| 57 | allowInArrayDestructuring: {
|
|---|
| 58 | type: "boolean",
|
|---|
| 59 | default: true
|
|---|
| 60 | },
|
|---|
| 61 | allowInObjectDestructuring: {
|
|---|
| 62 | type: "boolean",
|
|---|
| 63 | default: true
|
|---|
| 64 | }
|
|---|
| 65 | },
|
|---|
| 66 | additionalProperties: false
|
|---|
| 67 | }
|
|---|
| 68 | ],
|
|---|
| 69 |
|
|---|
| 70 | messages: {
|
|---|
| 71 | unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'."
|
|---|
| 72 | }
|
|---|
| 73 | },
|
|---|
| 74 |
|
|---|
| 75 | create(context) {
|
|---|
| 76 |
|
|---|
| 77 | const options = context.options[0] || {};
|
|---|
| 78 | const ALLOWED_VARIABLES = options.allow ? options.allow : [];
|
|---|
| 79 | const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
|
|---|
| 80 | const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
|
|---|
| 81 | const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
|
|---|
| 82 | const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
|
|---|
| 83 | const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false;
|
|---|
| 84 | const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
|
|---|
| 85 | const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true;
|
|---|
| 86 | const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true;
|
|---|
| 87 | const sourceCode = context.sourceCode;
|
|---|
| 88 |
|
|---|
| 89 | //-------------------------------------------------------------------------
|
|---|
| 90 | // Helpers
|
|---|
| 91 | //-------------------------------------------------------------------------
|
|---|
| 92 |
|
|---|
| 93 | /**
|
|---|
| 94 | * Check if identifier is present inside the allowed option
|
|---|
| 95 | * @param {string} identifier name of the node
|
|---|
| 96 | * @returns {boolean} true if its is present
|
|---|
| 97 | * @private
|
|---|
| 98 | */
|
|---|
| 99 | function isAllowed(identifier) {
|
|---|
| 100 | return ALLOWED_VARIABLES.includes(identifier);
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| 103 | /**
|
|---|
| 104 | * Check if identifier has a dangling underscore
|
|---|
| 105 | * @param {string} identifier name of the node
|
|---|
| 106 | * @returns {boolean} true if its is present
|
|---|
| 107 | * @private
|
|---|
| 108 | */
|
|---|
| 109 | function hasDanglingUnderscore(identifier) {
|
|---|
| 110 | const len = identifier.length;
|
|---|
| 111 |
|
|---|
| 112 | return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | /**
|
|---|
| 116 | * Check if identifier is a special case member expression
|
|---|
| 117 | * @param {string} identifier name of the node
|
|---|
| 118 | * @returns {boolean} true if its is a special case
|
|---|
| 119 | * @private
|
|---|
| 120 | */
|
|---|
| 121 | function isSpecialCaseIdentifierForMemberExpression(identifier) {
|
|---|
| 122 | return identifier === "__proto__";
|
|---|
| 123 | }
|
|---|
| 124 |
|
|---|
| 125 | /**
|
|---|
| 126 | * Check if identifier is a special case variable expression
|
|---|
| 127 | * @param {string} identifier name of the node
|
|---|
| 128 | * @returns {boolean} true if its is a special case
|
|---|
| 129 | * @private
|
|---|
| 130 | */
|
|---|
| 131 | function isSpecialCaseIdentifierInVariableExpression(identifier) {
|
|---|
| 132 |
|
|---|
| 133 | // Checks for the underscore library usage here
|
|---|
| 134 | return identifier === "_";
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | /**
|
|---|
| 138 | * Check if a node is a member reference of this.constructor
|
|---|
| 139 | * @param {ASTNode} node node to evaluate
|
|---|
| 140 | * @returns {boolean} true if it is a reference on this.constructor
|
|---|
| 141 | * @private
|
|---|
| 142 | */
|
|---|
| 143 | function isThisConstructorReference(node) {
|
|---|
| 144 | return node.object.type === "MemberExpression" &&
|
|---|
| 145 | node.object.property.name === "constructor" &&
|
|---|
| 146 | node.object.object.type === "ThisExpression";
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | /**
|
|---|
| 150 | * Check if function parameter has a dangling underscore.
|
|---|
| 151 | * @param {ASTNode} node function node to evaluate
|
|---|
| 152 | * @returns {void}
|
|---|
| 153 | * @private
|
|---|
| 154 | */
|
|---|
| 155 | function checkForDanglingUnderscoreInFunctionParameters(node) {
|
|---|
| 156 | if (!allowFunctionParams) {
|
|---|
| 157 | node.params.forEach(param => {
|
|---|
| 158 | const { type } = param;
|
|---|
| 159 | let nodeToCheck;
|
|---|
| 160 |
|
|---|
| 161 | if (type === "RestElement") {
|
|---|
| 162 | nodeToCheck = param.argument;
|
|---|
| 163 | } else if (type === "AssignmentPattern") {
|
|---|
| 164 | nodeToCheck = param.left;
|
|---|
| 165 | } else {
|
|---|
| 166 | nodeToCheck = param;
|
|---|
| 167 | }
|
|---|
| 168 |
|
|---|
| 169 | if (nodeToCheck.type === "Identifier") {
|
|---|
| 170 | const identifier = nodeToCheck.name;
|
|---|
| 171 |
|
|---|
| 172 | if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
|
|---|
| 173 | context.report({
|
|---|
| 174 | node: param,
|
|---|
| 175 | messageId: "unexpectedUnderscore",
|
|---|
| 176 | data: {
|
|---|
| 177 | identifier
|
|---|
| 178 | }
|
|---|
| 179 | });
|
|---|
| 180 | }
|
|---|
| 181 | }
|
|---|
| 182 | });
|
|---|
| 183 | }
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | /**
|
|---|
| 187 | * Check if function has a dangling underscore
|
|---|
| 188 | * @param {ASTNode} node node to evaluate
|
|---|
| 189 | * @returns {void}
|
|---|
| 190 | * @private
|
|---|
| 191 | */
|
|---|
| 192 | function checkForDanglingUnderscoreInFunction(node) {
|
|---|
| 193 | if (node.type === "FunctionDeclaration" && node.id) {
|
|---|
| 194 | const identifier = node.id.name;
|
|---|
| 195 |
|
|---|
| 196 | if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
|
|---|
| 197 | context.report({
|
|---|
| 198 | node,
|
|---|
| 199 | messageId: "unexpectedUnderscore",
|
|---|
| 200 | data: {
|
|---|
| 201 | identifier
|
|---|
| 202 | }
|
|---|
| 203 | });
|
|---|
| 204 | }
|
|---|
| 205 | }
|
|---|
| 206 | checkForDanglingUnderscoreInFunctionParameters(node);
|
|---|
| 207 | }
|
|---|
| 208 |
|
|---|
| 209 |
|
|---|
| 210 | /**
|
|---|
| 211 | * Check if variable expression has a dangling underscore
|
|---|
| 212 | * @param {ASTNode} node node to evaluate
|
|---|
| 213 | * @returns {void}
|
|---|
| 214 | * @private
|
|---|
| 215 | */
|
|---|
| 216 | function checkForDanglingUnderscoreInVariableExpression(node) {
|
|---|
| 217 | sourceCode.getDeclaredVariables(node).forEach(variable => {
|
|---|
| 218 | const definition = variable.defs.find(def => def.node === node);
|
|---|
| 219 | const identifierNode = definition.name;
|
|---|
| 220 | const identifier = identifierNode.name;
|
|---|
| 221 | let parent = identifierNode.parent;
|
|---|
| 222 |
|
|---|
| 223 | while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) {
|
|---|
| 224 | parent = parent.parent;
|
|---|
| 225 | }
|
|---|
| 226 |
|
|---|
| 227 | if (
|
|---|
| 228 | hasDanglingUnderscore(identifier) &&
|
|---|
| 229 | !isSpecialCaseIdentifierInVariableExpression(identifier) &&
|
|---|
| 230 | !isAllowed(identifier) &&
|
|---|
| 231 | !(allowInArrayDestructuring && parent.type === "ArrayPattern") &&
|
|---|
| 232 | !(allowInObjectDestructuring && parent.type === "ObjectPattern")
|
|---|
| 233 | ) {
|
|---|
| 234 | context.report({
|
|---|
| 235 | node,
|
|---|
| 236 | messageId: "unexpectedUnderscore",
|
|---|
| 237 | data: {
|
|---|
| 238 | identifier
|
|---|
| 239 | }
|
|---|
| 240 | });
|
|---|
| 241 | }
|
|---|
| 242 | });
|
|---|
| 243 | }
|
|---|
| 244 |
|
|---|
| 245 | /**
|
|---|
| 246 | * Check if member expression has a dangling underscore
|
|---|
| 247 | * @param {ASTNode} node node to evaluate
|
|---|
| 248 | * @returns {void}
|
|---|
| 249 | * @private
|
|---|
| 250 | */
|
|---|
| 251 | function checkForDanglingUnderscoreInMemberExpression(node) {
|
|---|
| 252 | const identifier = node.property.name,
|
|---|
| 253 | isMemberOfThis = node.object.type === "ThisExpression",
|
|---|
| 254 | isMemberOfSuper = node.object.type === "Super",
|
|---|
| 255 | isMemberOfThisConstructor = isThisConstructorReference(node);
|
|---|
| 256 |
|
|---|
| 257 | if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
|
|---|
| 258 | !(isMemberOfThis && allowAfterThis) &&
|
|---|
| 259 | !(isMemberOfSuper && allowAfterSuper) &&
|
|---|
| 260 | !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
|
|---|
| 261 | !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
|
|---|
| 262 | context.report({
|
|---|
| 263 | node,
|
|---|
| 264 | messageId: "unexpectedUnderscore",
|
|---|
| 265 | data: {
|
|---|
| 266 | identifier
|
|---|
| 267 | }
|
|---|
| 268 | });
|
|---|
| 269 | }
|
|---|
| 270 | }
|
|---|
| 271 |
|
|---|
| 272 | /**
|
|---|
| 273 | * Check if method declaration or method property has a dangling underscore
|
|---|
| 274 | * @param {ASTNode} node node to evaluate
|
|---|
| 275 | * @returns {void}
|
|---|
| 276 | * @private
|
|---|
| 277 | */
|
|---|
| 278 | function checkForDanglingUnderscoreInMethod(node) {
|
|---|
| 279 | const identifier = node.key.name;
|
|---|
| 280 | const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
|
|---|
| 281 |
|
|---|
| 282 | if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
|
|---|
| 283 | context.report({
|
|---|
| 284 | node,
|
|---|
| 285 | messageId: "unexpectedUnderscore",
|
|---|
| 286 | data: {
|
|---|
| 287 | identifier: node.key.type === "PrivateIdentifier"
|
|---|
| 288 | ? `#${identifier}`
|
|---|
| 289 | : identifier
|
|---|
| 290 | }
|
|---|
| 291 | });
|
|---|
| 292 | }
|
|---|
| 293 | }
|
|---|
| 294 |
|
|---|
| 295 | /**
|
|---|
| 296 | * Check if a class field has a dangling underscore
|
|---|
| 297 | * @param {ASTNode} node node to evaluate
|
|---|
| 298 | * @returns {void}
|
|---|
| 299 | * @private
|
|---|
| 300 | */
|
|---|
| 301 | function checkForDanglingUnderscoreInClassField(node) {
|
|---|
| 302 | const identifier = node.key.name;
|
|---|
| 303 |
|
|---|
| 304 | if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
|
|---|
| 305 | enforceInClassFields &&
|
|---|
| 306 | !isAllowed(identifier)) {
|
|---|
| 307 | context.report({
|
|---|
| 308 | node,
|
|---|
| 309 | messageId: "unexpectedUnderscore",
|
|---|
| 310 | data: {
|
|---|
| 311 | identifier: node.key.type === "PrivateIdentifier"
|
|---|
| 312 | ? `#${identifier}`
|
|---|
| 313 | : identifier
|
|---|
| 314 | }
|
|---|
| 315 | });
|
|---|
| 316 | }
|
|---|
| 317 | }
|
|---|
| 318 |
|
|---|
| 319 | //--------------------------------------------------------------------------
|
|---|
| 320 | // Public API
|
|---|
| 321 | //--------------------------------------------------------------------------
|
|---|
| 322 |
|
|---|
| 323 | return {
|
|---|
| 324 | FunctionDeclaration: checkForDanglingUnderscoreInFunction,
|
|---|
| 325 | VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
|
|---|
| 326 | MemberExpression: checkForDanglingUnderscoreInMemberExpression,
|
|---|
| 327 | MethodDefinition: checkForDanglingUnderscoreInMethod,
|
|---|
| 328 | PropertyDefinition: checkForDanglingUnderscoreInClassField,
|
|---|
| 329 | Property: checkForDanglingUnderscoreInMethod,
|
|---|
| 330 | FunctionExpression: checkForDanglingUnderscoreInFunction,
|
|---|
| 331 | ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
|
|---|
| 332 | };
|
|---|
| 333 |
|
|---|
| 334 | }
|
|---|
| 335 | };
|
|---|