| /** | |
|  * @fileoverview Rule to flag the use of redundant constructors in classes. | |
|  * @author Alberto Rodríguez | |
|  */ | |
| "use strict"; | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
|  | |
| /** | |
|  * Checks whether a given array of statements is a single call of `super`. | |
|  * @param {ASTNode[]} body An array of statements to check. | |
|  * @returns {boolean} `true` if the body is a single call of `super`. | |
|  */ | |
| function isSingleSuperCall(body) { | |
|     return ( | |
|         body.length === 1 && | |
|         body[0].type === "ExpressionStatement" && | |
|         body[0].expression.type === "CallExpression" && | |
|         body[0].expression.callee.type === "Super" | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Checks whether a given node is a pattern which doesn't have any side effects. | |
|  * Default parameters and Destructuring parameters can have side effects. | |
|  * @param {ASTNode} node A pattern node. | |
|  * @returns {boolean} `true` if the node doesn't have any side effects. | |
|  */ | |
| function isSimple(node) { | |
|     return node.type === "Identifier" || node.type === "RestElement"; | |
| } | |
| 
 | |
| /** | |
|  * Checks whether a given array of expressions is `...arguments` or not. | |
|  * `super(...arguments)` passes all arguments through. | |
|  * @param {ASTNode[]} superArgs An array of expressions to check. | |
|  * @returns {boolean} `true` if the superArgs is `...arguments`. | |
|  */ | |
| function isSpreadArguments(superArgs) { | |
|     return ( | |
|         superArgs.length === 1 && | |
|         superArgs[0].type === "SpreadElement" && | |
|         superArgs[0].argument.type === "Identifier" && | |
|         superArgs[0].argument.name === "arguments" | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Checks whether given 2 nodes are identifiers which have the same name or not. | |
|  * @param {ASTNode} ctorParam A node to check. | |
|  * @param {ASTNode} superArg A node to check. | |
|  * @returns {boolean} `true` if the nodes are identifiers which have the same | |
|  *      name. | |
|  */ | |
| function isValidIdentifierPair(ctorParam, superArg) { | |
|     return ( | |
|         ctorParam.type === "Identifier" && | |
|         superArg.type === "Identifier" && | |
|         ctorParam.name === superArg.name | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Checks whether given 2 nodes are a rest/spread pair which has the same values. | |
|  * @param {ASTNode} ctorParam A node to check. | |
|  * @param {ASTNode} superArg A node to check. | |
|  * @returns {boolean} `true` if the nodes are a rest/spread pair which has the | |
|  *      same values. | |
|  */ | |
| function isValidRestSpreadPair(ctorParam, superArg) { | |
|     return ( | |
|         ctorParam.type === "RestElement" && | |
|         superArg.type === "SpreadElement" && | |
|         isValidIdentifierPair(ctorParam.argument, superArg.argument) | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Checks whether given 2 nodes have the same value or not. | |
|  * @param {ASTNode} ctorParam A node to check. | |
|  * @param {ASTNode} superArg A node to check. | |
|  * @returns {boolean} `true` if the nodes have the same value or not. | |
|  */ | |
| function isValidPair(ctorParam, superArg) { | |
|     return ( | |
|         isValidIdentifierPair(ctorParam, superArg) || | |
|         isValidRestSpreadPair(ctorParam, superArg) | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Checks whether the parameters of a constructor and the arguments of `super()` | |
|  * have the same values or not. | |
|  * @param {ASTNode} ctorParams The parameters of a constructor to check. | |
|  * @param {ASTNode} superArgs The arguments of `super()` to check. | |
|  * @returns {boolean} `true` if those have the same values. | |
|  */ | |
| function isPassingThrough(ctorParams, superArgs) { | |
|     if (ctorParams.length !== superArgs.length) { | |
|         return false; | |
|     } | |
| 
 | |
|     for (let i = 0; i < ctorParams.length; ++i) { | |
|         if (!isValidPair(ctorParams[i], superArgs[i])) { | |
|             return false; | |
|         } | |
|     } | |
| 
 | |
|     return true; | |
| } | |
| 
 | |
| /** | |
|  * Checks whether the constructor body is a redundant super call. | |
|  * @param {Array} body constructor body content. | |
|  * @param {Array} ctorParams The params to check against super call. | |
|  * @returns {boolean} true if the constructor body is redundant | |
|  */ | |
| function isRedundantSuperCall(body, ctorParams) { | |
|     return ( | |
|         isSingleSuperCall(body) && | |
|         ctorParams.every(isSimple) && | |
|         ( | |
|             isSpreadArguments(body[0].expression.arguments) || | |
|             isPassingThrough(ctorParams, body[0].expression.arguments) | |
|         ) | |
|     ); | |
| } | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
|  | |
| /** @type {import('../shared/types').Rule} */ | |
| module.exports = { | |
|     meta: { | |
|         type: "suggestion", | |
| 
 | |
|         docs: { | |
|             description: "Disallow unnecessary constructors", | |
|             recommended: false, | |
|             url: "https://eslint.org/docs/latest/rules/no-useless-constructor" | |
|         }, | |
| 
 | |
|         schema: [], | |
| 
 | |
|         messages: { | |
|             noUselessConstructor: "Useless constructor." | |
|         } | |
|     }, | |
| 
 | |
|     create(context) { | |
| 
 | |
|         /** | |
|          * Checks whether a node is a redundant constructor | |
|          * @param {ASTNode} node node to check | |
|          * @returns {void} | |
|          */ | |
|         function checkForConstructor(node) { | |
|             if (node.kind !== "constructor") { | |
|                 return; | |
|             } | |
| 
 | |
|             /* | |
|              * Prevent crashing on parsers which do not require class constructor | |
|              * to have a body, e.g. typescript and flow | |
|              */ | |
|             if (!node.value.body) { | |
|                 return; | |
|             } | |
| 
 | |
|             const body = node.value.body.body; | |
|             const ctorParams = node.value.params; | |
|             const superClass = node.parent.parent.superClass; | |
| 
 | |
|             if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { | |
|                 context.report({ | |
|                     node, | |
|                     messageId: "noUselessConstructor" | |
|                 }); | |
|             } | |
|         } | |
| 
 | |
|         return { | |
|             MethodDefinition: checkForConstructor | |
|         }; | |
|     } | |
| };
 |