no-underscore-dangle.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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. },
  53. additionalProperties: false
  54. }
  55. ],
  56. messages: {
  57. unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'."
  58. }
  59. },
  60. create(context) {
  61. const options = context.options[0] || {};
  62. const ALLOWED_VARIABLES = options.allow ? options.allow : [];
  63. const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
  64. const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
  65. const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
  66. const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
  67. const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false;
  68. const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
  69. //-------------------------------------------------------------------------
  70. // Helpers
  71. //-------------------------------------------------------------------------
  72. /**
  73. * Check if identifier is present inside the allowed option
  74. * @param {string} identifier name of the node
  75. * @returns {boolean} true if its is present
  76. * @private
  77. */
  78. function isAllowed(identifier) {
  79. return ALLOWED_VARIABLES.includes(identifier);
  80. }
  81. /**
  82. * Check if identifier has a dangling underscore
  83. * @param {string} identifier name of the node
  84. * @returns {boolean} true if its is present
  85. * @private
  86. */
  87. function hasDanglingUnderscore(identifier) {
  88. const len = identifier.length;
  89. return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
  90. }
  91. /**
  92. * Check if identifier is a special case member expression
  93. * @param {string} identifier name of the node
  94. * @returns {boolean} true if its is a special case
  95. * @private
  96. */
  97. function isSpecialCaseIdentifierForMemberExpression(identifier) {
  98. return identifier === "__proto__";
  99. }
  100. /**
  101. * Check if identifier is a special case variable expression
  102. * @param {string} identifier name of the node
  103. * @returns {boolean} true if its is a special case
  104. * @private
  105. */
  106. function isSpecialCaseIdentifierInVariableExpression(identifier) {
  107. // Checks for the underscore library usage here
  108. return identifier === "_";
  109. }
  110. /**
  111. * Check if a node is a member reference of this.constructor
  112. * @param {ASTNode} node node to evaluate
  113. * @returns {boolean} true if it is a reference on this.constructor
  114. * @private
  115. */
  116. function isThisConstructorReference(node) {
  117. return node.object.type === "MemberExpression" &&
  118. node.object.property.name === "constructor" &&
  119. node.object.object.type === "ThisExpression";
  120. }
  121. /**
  122. * Check if function parameter has a dangling underscore.
  123. * @param {ASTNode} node function node to evaluate
  124. * @returns {void}
  125. * @private
  126. */
  127. function checkForDanglingUnderscoreInFunctionParameters(node) {
  128. if (!allowFunctionParams) {
  129. node.params.forEach(param => {
  130. const { type } = param;
  131. let nodeToCheck;
  132. if (type === "RestElement") {
  133. nodeToCheck = param.argument;
  134. } else if (type === "AssignmentPattern") {
  135. nodeToCheck = param.left;
  136. } else {
  137. nodeToCheck = param;
  138. }
  139. if (nodeToCheck.type === "Identifier") {
  140. const identifier = nodeToCheck.name;
  141. if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
  142. context.report({
  143. node: param,
  144. messageId: "unexpectedUnderscore",
  145. data: {
  146. identifier
  147. }
  148. });
  149. }
  150. }
  151. });
  152. }
  153. }
  154. /**
  155. * Check if function has a dangling underscore
  156. * @param {ASTNode} node node to evaluate
  157. * @returns {void}
  158. * @private
  159. */
  160. function checkForDanglingUnderscoreInFunction(node) {
  161. if (node.type === "FunctionDeclaration" && node.id) {
  162. const identifier = node.id.name;
  163. if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
  164. context.report({
  165. node,
  166. messageId: "unexpectedUnderscore",
  167. data: {
  168. identifier
  169. }
  170. });
  171. }
  172. }
  173. checkForDanglingUnderscoreInFunctionParameters(node);
  174. }
  175. /**
  176. * Check if variable expression has a dangling underscore
  177. * @param {ASTNode} node node to evaluate
  178. * @returns {void}
  179. * @private
  180. */
  181. function checkForDanglingUnderscoreInVariableExpression(node) {
  182. const identifier = node.id.name;
  183. if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
  184. !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
  185. context.report({
  186. node,
  187. messageId: "unexpectedUnderscore",
  188. data: {
  189. identifier
  190. }
  191. });
  192. }
  193. }
  194. /**
  195. * Check if member expression has a dangling underscore
  196. * @param {ASTNode} node node to evaluate
  197. * @returns {void}
  198. * @private
  199. */
  200. function checkForDanglingUnderscoreInMemberExpression(node) {
  201. const identifier = node.property.name,
  202. isMemberOfThis = node.object.type === "ThisExpression",
  203. isMemberOfSuper = node.object.type === "Super",
  204. isMemberOfThisConstructor = isThisConstructorReference(node);
  205. if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
  206. !(isMemberOfThis && allowAfterThis) &&
  207. !(isMemberOfSuper && allowAfterSuper) &&
  208. !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
  209. !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
  210. context.report({
  211. node,
  212. messageId: "unexpectedUnderscore",
  213. data: {
  214. identifier
  215. }
  216. });
  217. }
  218. }
  219. /**
  220. * Check if method declaration or method property has a dangling underscore
  221. * @param {ASTNode} node node to evaluate
  222. * @returns {void}
  223. * @private
  224. */
  225. function checkForDanglingUnderscoreInMethod(node) {
  226. const identifier = node.key.name;
  227. const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
  228. if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
  229. context.report({
  230. node,
  231. messageId: "unexpectedUnderscore",
  232. data: {
  233. identifier: node.key.type === "PrivateIdentifier"
  234. ? `#${identifier}`
  235. : identifier
  236. }
  237. });
  238. }
  239. }
  240. /**
  241. * Check if a class field has a dangling underscore
  242. * @param {ASTNode} node node to evaluate
  243. * @returns {void}
  244. * @private
  245. */
  246. function checkForDanglingUnderscoreInClassField(node) {
  247. const identifier = node.key.name;
  248. if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
  249. enforceInClassFields &&
  250. !isAllowed(identifier)) {
  251. context.report({
  252. node,
  253. messageId: "unexpectedUnderscore",
  254. data: {
  255. identifier: node.key.type === "PrivateIdentifier"
  256. ? `#${identifier}`
  257. : identifier
  258. }
  259. });
  260. }
  261. }
  262. //--------------------------------------------------------------------------
  263. // Public API
  264. //--------------------------------------------------------------------------
  265. return {
  266. FunctionDeclaration: checkForDanglingUnderscoreInFunction,
  267. VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
  268. MemberExpression: checkForDanglingUnderscoreInMemberExpression,
  269. MethodDefinition: checkForDanglingUnderscoreInMethod,
  270. PropertyDefinition: checkForDanglingUnderscoreInClassField,
  271. Property: checkForDanglingUnderscoreInMethod,
  272. FunctionExpression: checkForDanglingUnderscoreInFunction,
  273. ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
  274. };
  275. }
  276. };