• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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