no-underscore-dangle.js 13 KB

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