codtracker-js/node_modules/eslint/lib/rules/no-useless-constructor.js
2025-04-19 23:12:19 -04:00

243 lines
7.1 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,
};
},
};