no-underscore-dangle.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * @fileoverview Rule to flag trailing underscores in variable declarations.
  3. * @author Matt DuVall <http://www.mattduvall.com>
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. type: "suggestion",
  12. docs: {
  13. description: "disallow dangling underscores in identifiers",
  14. category: "Stylistic Issues",
  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. enforceInMethodNames: {
  37. type: "boolean",
  38. default: false
  39. }
  40. },
  41. additionalProperties: false
  42. }
  43. ]
  44. },
  45. create(context) {
  46. const options = context.options[0] || {};
  47. const ALLOWED_VARIABLES = options.allow ? options.allow : [];
  48. const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
  49. const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
  50. const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
  51. //-------------------------------------------------------------------------
  52. // Helpers
  53. //-------------------------------------------------------------------------
  54. /**
  55. * Check if identifier is present inside the allowed option
  56. * @param {string} identifier name of the node
  57. * @returns {boolean} true if its is present
  58. * @private
  59. */
  60. function isAllowed(identifier) {
  61. return ALLOWED_VARIABLES.some(ident => ident === identifier);
  62. }
  63. /**
  64. * Check if identifier has a underscore at the end
  65. * @param {ASTNode} identifier node to evaluate
  66. * @returns {boolean} true if its is present
  67. * @private
  68. */
  69. function hasTrailingUnderscore(identifier) {
  70. const len = identifier.length;
  71. return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
  72. }
  73. /**
  74. * Check if identifier is a special case member expression
  75. * @param {ASTNode} identifier node to evaluate
  76. * @returns {boolean} true if its is a special case
  77. * @private
  78. */
  79. function isSpecialCaseIdentifierForMemberExpression(identifier) {
  80. return identifier === "__proto__";
  81. }
  82. /**
  83. * Check if identifier is a special case variable expression
  84. * @param {ASTNode} identifier node to evaluate
  85. * @returns {boolean} true if its is a special case
  86. * @private
  87. */
  88. function isSpecialCaseIdentifierInVariableExpression(identifier) {
  89. // Checks for the underscore library usage here
  90. return identifier === "_";
  91. }
  92. /**
  93. * Check if function has a underscore at the end
  94. * @param {ASTNode} node node to evaluate
  95. * @returns {void}
  96. * @private
  97. */
  98. function checkForTrailingUnderscoreInFunctionDeclaration(node) {
  99. if (node.id) {
  100. const identifier = node.id.name;
  101. if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
  102. context.report({
  103. node,
  104. message: "Unexpected dangling '_' in '{{identifier}}'.",
  105. data: {
  106. identifier
  107. }
  108. });
  109. }
  110. }
  111. }
  112. /**
  113. * Check if variable expression has a underscore at the end
  114. * @param {ASTNode} node node to evaluate
  115. * @returns {void}
  116. * @private
  117. */
  118. function checkForTrailingUnderscoreInVariableExpression(node) {
  119. const identifier = node.id.name;
  120. if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
  121. !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
  122. context.report({
  123. node,
  124. message: "Unexpected dangling '_' in '{{identifier}}'.",
  125. data: {
  126. identifier
  127. }
  128. });
  129. }
  130. }
  131. /**
  132. * Check if member expression has a underscore at the end
  133. * @param {ASTNode} node node to evaluate
  134. * @returns {void}
  135. * @private
  136. */
  137. function checkForTrailingUnderscoreInMemberExpression(node) {
  138. const identifier = node.property.name,
  139. isMemberOfThis = node.object.type === "ThisExpression",
  140. isMemberOfSuper = node.object.type === "Super";
  141. if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
  142. !(isMemberOfThis && allowAfterThis) &&
  143. !(isMemberOfSuper && allowAfterSuper) &&
  144. !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
  145. context.report({
  146. node,
  147. message: "Unexpected dangling '_' in '{{identifier}}'.",
  148. data: {
  149. identifier
  150. }
  151. });
  152. }
  153. }
  154. /**
  155. * Check if method declaration or method property has a underscore at the end
  156. * @param {ASTNode} node node to evaluate
  157. * @returns {void}
  158. * @private
  159. */
  160. function checkForTrailingUnderscoreInMethod(node) {
  161. const identifier = node.key.name;
  162. const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
  163. if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier)) {
  164. context.report({
  165. node,
  166. message: "Unexpected dangling '_' in '{{identifier}}'.",
  167. data: {
  168. identifier
  169. }
  170. });
  171. }
  172. }
  173. //--------------------------------------------------------------------------
  174. // Public API
  175. //--------------------------------------------------------------------------
  176. return {
  177. FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
  178. VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
  179. MemberExpression: checkForTrailingUnderscoreInMemberExpression,
  180. MethodDefinition: checkForTrailingUnderscoreInMethod,
  181. Property: checkForTrailingUnderscoreInMethod
  182. };
  183. }
  184. };