no-useless-constructor.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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 any of a method's parameters have a decorator or are a parameter property.
  12. * @param {ASTNode} node A method definition node.
  13. * @returns {boolean} `true` if any parameter had a decorator or is a parameter property.
  14. */
  15. function hasDecoratorsOrParameterProperty(node) {
  16. return node.value.params.some(
  17. param =>
  18. param.decorators?.length || param.type === "TSParameterProperty",
  19. );
  20. }
  21. /**
  22. * Checks whether a node's accessibility makes it not useless.
  23. * @param {ASTNode} node A method definition node.
  24. * @returns {boolean} `true` if the node has a useful accessibility.
  25. */
  26. function hasUsefulAccessibility(node) {
  27. switch (node.accessibility) {
  28. case "protected":
  29. case "private":
  30. return true;
  31. case "public":
  32. return !!node.parent.parent.superClass;
  33. default:
  34. return false;
  35. }
  36. }
  37. /**
  38. * Checks whether a given array of statements is a single call of `super`.
  39. * @param {ASTNode[]} body An array of statements to check.
  40. * @returns {boolean} `true` if the body is a single call of `super`.
  41. */
  42. function isSingleSuperCall(body) {
  43. return (
  44. body.length === 1 &&
  45. body[0].type === "ExpressionStatement" &&
  46. body[0].expression.type === "CallExpression" &&
  47. body[0].expression.callee.type === "Super"
  48. );
  49. }
  50. /**
  51. * Checks whether a given node is a pattern which doesn't have any side effects.
  52. * Default parameters and Destructuring parameters can have side effects.
  53. * @param {ASTNode} node A pattern node.
  54. * @returns {boolean} `true` if the node doesn't have any side effects.
  55. */
  56. function isSimple(node) {
  57. return node.type === "Identifier" || node.type === "RestElement";
  58. }
  59. /**
  60. * Checks whether a given array of expressions is `...arguments` or not.
  61. * `super(...arguments)` passes all arguments through.
  62. * @param {ASTNode[]} superArgs An array of expressions to check.
  63. * @returns {boolean} `true` if the superArgs is `...arguments`.
  64. */
  65. function isSpreadArguments(superArgs) {
  66. return (
  67. superArgs.length === 1 &&
  68. superArgs[0].type === "SpreadElement" &&
  69. superArgs[0].argument.type === "Identifier" &&
  70. superArgs[0].argument.name === "arguments"
  71. );
  72. }
  73. /**
  74. * Checks whether given 2 nodes are identifiers which have the same name or not.
  75. * @param {ASTNode} ctorParam A node to check.
  76. * @param {ASTNode} superArg A node to check.
  77. * @returns {boolean} `true` if the nodes are identifiers which have the same
  78. * name.
  79. */
  80. function isValidIdentifierPair(ctorParam, superArg) {
  81. return (
  82. ctorParam.type === "Identifier" &&
  83. superArg.type === "Identifier" &&
  84. ctorParam.name === superArg.name
  85. );
  86. }
  87. /**
  88. * Checks whether given 2 nodes are a rest/spread pair which has the same values.
  89. * @param {ASTNode} ctorParam A node to check.
  90. * @param {ASTNode} superArg A node to check.
  91. * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
  92. * same values.
  93. */
  94. function isValidRestSpreadPair(ctorParam, superArg) {
  95. return (
  96. ctorParam.type === "RestElement" &&
  97. superArg.type === "SpreadElement" &&
  98. isValidIdentifierPair(ctorParam.argument, superArg.argument)
  99. );
  100. }
  101. /**
  102. * Checks whether given 2 nodes have the same value or not.
  103. * @param {ASTNode} ctorParam A node to check.
  104. * @param {ASTNode} superArg A node to check.
  105. * @returns {boolean} `true` if the nodes have the same value or not.
  106. */
  107. function isValidPair(ctorParam, superArg) {
  108. return (
  109. isValidIdentifierPair(ctorParam, superArg) ||
  110. isValidRestSpreadPair(ctorParam, superArg)
  111. );
  112. }
  113. /**
  114. * Checks whether the parameters of a constructor and the arguments of `super()`
  115. * have the same values or not.
  116. * @param {ASTNode} ctorParams The parameters of a constructor to check.
  117. * @param {ASTNode} superArgs The arguments of `super()` to check.
  118. * @returns {boolean} `true` if those have the same values.
  119. */
  120. function isPassingThrough(ctorParams, superArgs) {
  121. if (ctorParams.length !== superArgs.length) {
  122. return false;
  123. }
  124. for (let i = 0; i < ctorParams.length; ++i) {
  125. if (!isValidPair(ctorParams[i], superArgs[i])) {
  126. return false;
  127. }
  128. }
  129. return true;
  130. }
  131. /**
  132. * Checks whether the constructor body is a redundant super call.
  133. * @param {Array} body constructor body content.
  134. * @param {Array} ctorParams The params to check against super call.
  135. * @returns {boolean} true if the constructor body is redundant
  136. */
  137. function isRedundantSuperCall(body, ctorParams) {
  138. return (
  139. isSingleSuperCall(body) &&
  140. ctorParams.every(isSimple) &&
  141. (isSpreadArguments(body[0].expression.arguments) ||
  142. isPassingThrough(ctorParams, body[0].expression.arguments))
  143. );
  144. }
  145. //------------------------------------------------------------------------------
  146. // Rule Definition
  147. //------------------------------------------------------------------------------
  148. /** @type {import('../types').Rule.RuleModule} */
  149. module.exports = {
  150. meta: {
  151. dialects: ["javascript", "typescript"],
  152. language: "javascript",
  153. type: "suggestion",
  154. docs: {
  155. description: "Disallow unnecessary constructors",
  156. recommended: false,
  157. url: "https://eslint.org/docs/latest/rules/no-useless-constructor",
  158. },
  159. hasSuggestions: true,
  160. schema: [],
  161. messages: {
  162. noUselessConstructor: "Useless constructor.",
  163. removeConstructor: "Remove the constructor.",
  164. },
  165. },
  166. create(context) {
  167. /**
  168. * Checks whether a node is a redundant constructor
  169. * @param {ASTNode} node node to check
  170. * @returns {void}
  171. */
  172. function checkForConstructor(node) {
  173. if (
  174. node.kind !== "constructor" ||
  175. node.value.type !== "FunctionExpression" ||
  176. hasDecoratorsOrParameterProperty(node) ||
  177. hasUsefulAccessibility(node)
  178. ) {
  179. return;
  180. }
  181. /*
  182. * Prevent crashing on parsers which do not require class constructor
  183. * to have a body, e.g. typescript and flow
  184. */
  185. if (!node.value.body) {
  186. return;
  187. }
  188. const body = node.value.body.body;
  189. const ctorParams = node.value.params;
  190. const superClass = node.parent.parent.superClass;
  191. if (
  192. superClass
  193. ? isRedundantSuperCall(body, ctorParams)
  194. : body.length === 0
  195. ) {
  196. context.report({
  197. node,
  198. messageId: "noUselessConstructor",
  199. suggest: [
  200. {
  201. messageId: "removeConstructor",
  202. *fix(fixer) {
  203. const nextToken =
  204. context.sourceCode.getTokenAfter(node);
  205. const addSemiColon =
  206. nextToken.type === "Punctuator" &&
  207. nextToken.value === "[" &&
  208. astUtils.needsPrecedingSemicolon(
  209. context.sourceCode,
  210. node,
  211. );
  212. yield fixer.replaceText(
  213. node,
  214. addSemiColon ? ";" : "",
  215. );
  216. },
  217. },
  218. ],
  219. });
  220. }
  221. }
  222. return {
  223. MethodDefinition: checkForConstructor,
  224. };
  225. },
  226. };