no-useless-constructor.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * @fileoverview Rule to flag the use of redundant constructors in classes.
  3. * @author Alberto Rodríguez
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Helpers
  9. //------------------------------------------------------------------------------
  10. /**
  11. * Checks whether a given array of statements is a single call of `super`.
  12. * @param {ASTNode[]} body An array of statements to check.
  13. * @returns {boolean} `true` if the body is a single call of `super`.
  14. */
  15. function isSingleSuperCall(body) {
  16. return (
  17. body.length === 1 &&
  18. body[0].type === "ExpressionStatement" &&
  19. body[0].expression.type === "CallExpression" &&
  20. body[0].expression.callee.type === "Super"
  21. );
  22. }
  23. /**
  24. * Checks whether a given node is a pattern which doesn't have any side effects.
  25. * Default parameters and Destructuring parameters can have side effects.
  26. * @param {ASTNode} node A pattern node.
  27. * @returns {boolean} `true` if the node doesn't have any side effects.
  28. */
  29. function isSimple(node) {
  30. return node.type === "Identifier" || node.type === "RestElement";
  31. }
  32. /**
  33. * Checks whether a given array of expressions is `...arguments` or not.
  34. * `super(...arguments)` passes all arguments through.
  35. * @param {ASTNode[]} superArgs An array of expressions to check.
  36. * @returns {boolean} `true` if the superArgs is `...arguments`.
  37. */
  38. function isSpreadArguments(superArgs) {
  39. return (
  40. superArgs.length === 1 &&
  41. superArgs[0].type === "SpreadElement" &&
  42. superArgs[0].argument.type === "Identifier" &&
  43. superArgs[0].argument.name === "arguments"
  44. );
  45. }
  46. /**
  47. * Checks whether given 2 nodes are identifiers which have the same name or not.
  48. * @param {ASTNode} ctorParam A node to check.
  49. * @param {ASTNode} superArg A node to check.
  50. * @returns {boolean} `true` if the nodes are identifiers which have the same
  51. * name.
  52. */
  53. function isValidIdentifierPair(ctorParam, superArg) {
  54. return (
  55. ctorParam.type === "Identifier" &&
  56. superArg.type === "Identifier" &&
  57. ctorParam.name === superArg.name
  58. );
  59. }
  60. /**
  61. * Checks whether given 2 nodes are a rest/spread pair which has the same values.
  62. * @param {ASTNode} ctorParam A node to check.
  63. * @param {ASTNode} superArg A node to check.
  64. * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
  65. * same values.
  66. */
  67. function isValidRestSpreadPair(ctorParam, superArg) {
  68. return (
  69. ctorParam.type === "RestElement" &&
  70. superArg.type === "SpreadElement" &&
  71. isValidIdentifierPair(ctorParam.argument, superArg.argument)
  72. );
  73. }
  74. /**
  75. * Checks whether given 2 nodes have the same value or not.
  76. * @param {ASTNode} ctorParam A node to check.
  77. * @param {ASTNode} superArg A node to check.
  78. * @returns {boolean} `true` if the nodes have the same value or not.
  79. */
  80. function isValidPair(ctorParam, superArg) {
  81. return (
  82. isValidIdentifierPair(ctorParam, superArg) ||
  83. isValidRestSpreadPair(ctorParam, superArg)
  84. );
  85. }
  86. /**
  87. * Checks whether the parameters of a constructor and the arguments of `super()`
  88. * have the same values or not.
  89. * @param {ASTNode} ctorParams The parameters of a constructor to check.
  90. * @param {ASTNode} superArgs The arguments of `super()` to check.
  91. * @returns {boolean} `true` if those have the same values.
  92. */
  93. function isPassingThrough(ctorParams, superArgs) {
  94. if (ctorParams.length !== superArgs.length) {
  95. return false;
  96. }
  97. for (let i = 0; i < ctorParams.length; ++i) {
  98. if (!isValidPair(ctorParams[i], superArgs[i])) {
  99. return false;
  100. }
  101. }
  102. return true;
  103. }
  104. /**
  105. * Checks whether the constructor body is a redundant super call.
  106. * @param {Array} body constructor body content.
  107. * @param {Array} ctorParams The params to check against super call.
  108. * @returns {boolean} true if the constructor body is redundant
  109. */
  110. function isRedundantSuperCall(body, ctorParams) {
  111. return (
  112. isSingleSuperCall(body) &&
  113. ctorParams.every(isSimple) &&
  114. (
  115. isSpreadArguments(body[0].expression.arguments) ||
  116. isPassingThrough(ctorParams, body[0].expression.arguments)
  117. )
  118. );
  119. }
  120. //------------------------------------------------------------------------------
  121. // Rule Definition
  122. //------------------------------------------------------------------------------
  123. /** @type {import('../shared/types').Rule} */
  124. module.exports = {
  125. meta: {
  126. type: "suggestion",
  127. docs: {
  128. description: "Disallow unnecessary constructors",
  129. recommended: false,
  130. url: "https://eslint.org/docs/latest/rules/no-useless-constructor"
  131. },
  132. hasSuggestions: true,
  133. schema: [],
  134. messages: {
  135. noUselessConstructor: "Useless constructor.",
  136. removeConstructor: "Remove the constructor."
  137. }
  138. },
  139. create(context) {
  140. /**
  141. * Checks whether a node is a redundant constructor
  142. * @param {ASTNode} node node to check
  143. * @returns {void}
  144. */
  145. function checkForConstructor(node) {
  146. if (node.kind !== "constructor") {
  147. return;
  148. }
  149. /*
  150. * Prevent crashing on parsers which do not require class constructor
  151. * to have a body, e.g. typescript and flow
  152. */
  153. if (!node.value.body) {
  154. return;
  155. }
  156. const body = node.value.body.body;
  157. const ctorParams = node.value.params;
  158. const superClass = node.parent.parent.superClass;
  159. if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) {
  160. context.report({
  161. node,
  162. messageId: "noUselessConstructor",
  163. suggest: [
  164. {
  165. messageId: "removeConstructor",
  166. *fix(fixer) {
  167. const nextToken = context.sourceCode.getTokenAfter(node);
  168. const addSemiColon = nextToken.type === "Punctuator" && nextToken.value === "[" && astUtils.needsPrecedingSemicolon(context.sourceCode, node);
  169. yield fixer.replaceText(node, addSemiColon ? ";" : "");
  170. }
  171. }
  172. ]
  173. });
  174. }
  175. }
  176. return {
  177. MethodDefinition: checkForConstructor
  178. };
  179. }
  180. };