UNPKG

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