190 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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
 | |
|         };
 | |
|     }
 | |
| };
 |