no-underscore-dangle.js 9.3 KB

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