UNPKG

7.65 kBJavaScriptView Raw
1/**
2 * @fileoverview A rule to verify `super()` callings in constructor.
3 * @author Toru Nagashima
4 * @copyright 2015 Toru Nagashima. All rights reserved.
5 */
6
7"use strict";
8
9//------------------------------------------------------------------------------
10// Requirements
11//------------------------------------------------------------------------------
12
13var astUtils = require("../ast-utils");
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19/**
20 * Checks whether or not a given node is a constructor.
21 * @param {ASTNode} node - A node to check. This node type is one of
22 * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
23 * `ArrowFunctionExpression`.
24 * @returns {boolean} `true` if the node is a constructor.
25 */
26function isConstructorFunction(node) {
27 return (
28 node.type === "FunctionExpression" &&
29 node.parent.type === "MethodDefinition" &&
30 node.parent.kind === "constructor"
31 );
32}
33
34//------------------------------------------------------------------------------
35// Rule Definition
36//------------------------------------------------------------------------------
37
38module.exports = function(context) {
39 // {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
40 // Information for each constructor.
41 // - upper: Information of the upper constructor.
42 // - hasExtends: A flag which shows whether own class has a valid `extends`
43 // part.
44 // - scope: The scope of own class.
45 // - codePath: The code path object of the constructor.
46 var funcInfo = null;
47
48 // {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
49 // Information for each code path segment.
50 // - calledInSomePaths: A flag of be called `super()` in some code paths.
51 // - calledInEveryPaths: A flag of be called `super()` in all code paths.
52 var segInfoMap = Object.create(null);
53
54 /**
55 * Gets the flag which shows `super()` is called in some paths.
56 * @param {CodePathSegment} segment - A code path segment to get.
57 * @returns {boolean} The flag which shows `super()` is called in some paths
58 */
59 function isCalledInSomePath(segment) {
60 return segInfoMap[segment.id].calledInSomePaths;
61 }
62
63 /**
64 * Gets the flag which shows `super()` is called in all paths.
65 * @param {CodePathSegment} segment - A code path segment to get.
66 * @returns {boolean} The flag which shows `super()` is called in all paths.
67 */
68 function isCalledInEveryPath(segment) {
69 return segInfoMap[segment.id].calledInEveryPaths;
70 }
71
72 return {
73 /**
74 * Stacks a constructor information.
75 * @param {CodePath} codePath - A code path which was started.
76 * @param {ASTNode} node - The current node.
77 * @returns {void}
78 */
79 "onCodePathStart": function(codePath, node) {
80 if (!isConstructorFunction(node)) {
81 return;
82 }
83
84 // Class > ClassBody > MethodDefinition > FunctionExpression
85 var classNode = node.parent.parent.parent;
86 funcInfo = {
87 upper: funcInfo,
88 hasExtends: Boolean(
89 classNode.superClass &&
90 !astUtils.isNullOrUndefined(classNode.superClass)
91 ),
92 scope: context.getScope(),
93 codePath: codePath
94 };
95 },
96
97 /**
98 * Pops a constructor information.
99 * And reports if `super()` lacked.
100 * @param {CodePath} codePath - A code path which was ended.
101 * @param {ASTNode} node - The current node.
102 * @returns {void}
103 */
104 "onCodePathEnd": function(codePath, node) {
105 if (!isConstructorFunction(node)) {
106 return;
107 }
108
109 // Skip if own class which has a valid `extends` part.
110 var hasExtends = funcInfo.hasExtends;
111 funcInfo = funcInfo.upper;
112 if (!hasExtends) {
113 return;
114 }
115
116 // Reports if `super()` lacked.
117 var segments = codePath.returnedSegments;
118 var calledInEveryPaths = segments.every(isCalledInEveryPath);
119 var calledInSomePaths = segments.some(isCalledInSomePath);
120 if (!calledInEveryPaths) {
121 context.report({
122 message: calledInSomePaths ?
123 "Lacked a call of 'super()' in some code paths." :
124 "Expected to call 'super()'.",
125 node: node.parent
126 });
127 }
128 },
129
130 /**
131 * Initialize information of a given code path segment.
132 * @param {CodePathSegment} segment - A code path segment to initialize.
133 * @returns {void}
134 */
135 "onCodePathSegmentStart": function(segment) {
136 // Skip if this is not in a constructor of a class which has a valid
137 // `extends` part.
138 if (!(
139 funcInfo &&
140 funcInfo.hasExtends &&
141 funcInfo.scope === context.getScope().variableScope
142 )) {
143 return;
144 }
145
146 // Initialize info.
147 var info = segInfoMap[segment.id] = {
148 calledInSomePaths: false,
149 calledInEveryPaths: false
150 };
151
152 // When there are previous segments, aggregates these.
153 var prevSegments = segment.prevSegments;
154 if (prevSegments.length > 0) {
155 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
156 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
157 }
158 },
159
160 /**
161 * Checks for a call of `super()`.
162 * @param {ASTNode} node - A CallExpression node to check.
163 * @returns {void}
164 */
165 "CallExpression:exit": function(node) {
166 // Skip if the node is not `super()`.
167 if (node.callee.type !== "Super") {
168 return;
169 }
170
171 // Skip if this is not in a constructor.
172 if (!(funcInfo && funcInfo.scope === context.getScope().variableScope)) {
173 return;
174 }
175
176 // Reports if needed.
177 if (funcInfo.hasExtends) {
178 // This class has a valid `extends` part.
179 // Checks duplicate `super()`;
180 var segments = funcInfo.codePath.currentSegments;
181 var duplicate = false;
182 for (var i = 0; i < segments.length; ++i) {
183 var info = segInfoMap[segments[i].id];
184
185 duplicate = duplicate || info.calledInSomePaths;
186 info.calledInSomePaths = info.calledInEveryPaths = true;
187 }
188
189 if (duplicate) {
190 context.report({
191 message: "Unexpected duplicate 'super()'.",
192 node: node
193 });
194 }
195 } else {
196 // This class does not have a valid `extends` part.
197 // Disallow `super()`.
198 context.report({
199 message: "Unexpected 'super()'.",
200 node: node
201 });
202 }
203 },
204
205 /**
206 * Resets state.
207 * @returns {void}
208 */
209 "Program:exit": function() {
210 segInfoMap = Object.create(null);
211 }
212 };
213};
214
215module.exports.schema = [];