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