| @@ -0,0 +1,277 @@ |
| | 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 | module.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 | }; |