codtracker-js/node_modules/eslint/lib/rules/no-useless-constructor.js
2025-04-17 07:44:37 -04:00

253 lines
6.8 KiB
JavaScript

/**
* @fileoverview Rule to flag the use of redundant constructors in classes.
* @author Alberto Rodríguez
*/
"use strict";
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether any of a method's parameters have a decorator or are a parameter property.
* @param {ASTNode} node A method definition node.
* @returns {boolean} `true` if any parameter had a decorator or is a parameter property.
*/
function hasDecoratorsOrParameterProperty(node) {
return node.value.params.some(
param =>
param.decorators?.length || param.type === "TSParameterProperty",
);
}
/**
* Checks whether a node's accessibility makes it not useless.
* @param {ASTNode} node A method definition node.
* @returns {boolean} `true` if the node has a useful accessibility.
*/
function hasUsefulAccessibility(node) {
switch (node.accessibility) {
case "protected":
case "private":
return true;
case "public":
return !!node.parent.parent.superClass;
default:
return false;
}
}
/**
* 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('../types').Rule.RuleModule} */
module.exports = {
meta: {
dialects: ["javascript", "typescript"],
language: "javascript",
type: "suggestion",
docs: {
description: "Disallow unnecessary constructors",
recommended: false,
url: "https://eslint.org/docs/latest/rules/no-useless-constructor",
},
hasSuggestions: true,
schema: [],
messages: {
noUselessConstructor: "Useless constructor.",
removeConstructor: "Remove the 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" ||
node.value.type !== "FunctionExpression" ||
hasDecoratorsOrParameterProperty(node) ||
hasUsefulAccessibility(node)
) {
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",
suggest: [
{
messageId: "removeConstructor",
*fix(fixer) {
const nextToken =
context.sourceCode.getTokenAfter(node);
const addSemiColon =
nextToken.type === "Punctuator" &&
nextToken.value === "[" &&
astUtils.needsPrecedingSemicolon(
context.sourceCode,
node,
);
yield fixer.replaceText(
node,
addSemiColon ? ";" : "",
);
},
},
],
});
}
}
return {
MethodDefinition: checkForConstructor,
};
},
};