no-underscore-dangle.js 12 KB

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