UNPKG

6.14 kBJavaScriptView Raw
1/**
2 * @fileoverview A rule to disallow using `this`/`super` before `super()`.
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}[]}
40 // Information for each constructor.
41 // - upper: Information of the upper constructor.
42 // - hasExtends: A flag which shows whether the owner class has a valid
43 // `extends` part.
44 // - scope: The scope of the owner class.
45 // - codePath: The code path of this constructor.
46 var funcInfo = null;
47
48 // {Map<string, boolean>}
49 // Information for each code path segment.
50 // The value is a flag which shows `super()` called in all code paths.
51 var segInfoMap = Object.create(null);
52
53 /**
54 * Gets whether or not `super()` is called in a given code path segment.
55 * @param {CodePathSegment} segment - A code path segment to get.
56 * @returns {boolean} `true` if `super()` is called.
57 */
58 function isCalled(segment) {
59 return Boolean(segInfoMap[segment.id]);
60 }
61
62 /**
63 * Checks whether or not this is in a constructor.
64 * @returns {boolean} `true` if this is in a constructor.
65 */
66 function isInConstructor() {
67 return Boolean(
68 funcInfo &&
69 funcInfo.hasExtends &&
70 funcInfo.scope === context.getScope().variableScope
71 );
72 }
73
74 /**
75 * Checks whether or not this is before `super()` is called.
76 * @returns {boolean} `true` if this is before `super()` is called.
77 */
78 function isBeforeCallOfSuper() {
79 return (
80 isInConstructor(funcInfo) &&
81 !funcInfo.codePath.currentSegments.every(isCalled)
82 );
83 }
84
85 return {
86 /**
87 * Stacks a constructor information.
88 * @param {CodePath} codePath - A code path which was started.
89 * @param {ASTNode} node - The current node.
90 * @returns {void}
91 */
92 "onCodePathStart": function(codePath, node) {
93 if (!isConstructorFunction(node)) {
94 return;
95 }
96
97 // Class > ClassBody > MethodDefinition > FunctionExpression
98 var classNode = node.parent.parent.parent;
99 funcInfo = {
100 upper: funcInfo,
101 hasExtends: Boolean(
102 classNode.superClass &&
103 !astUtils.isNullOrUndefined(classNode.superClass)
104 ),
105 scope: context.getScope(),
106 codePath: codePath
107 };
108 },
109
110 /**
111 * Pops a constructor information.
112 * @param {CodePath} codePath - A code path which was ended.
113 * @param {ASTNode} node - The current node.
114 * @returns {void}
115 */
116 "onCodePathEnd": function(codePath, node) {
117 if (isConstructorFunction(node)) {
118 funcInfo = funcInfo.upper;
119 }
120 },
121
122 /**
123 * Initialize information of a given code path segment.
124 * @param {CodePathSegment} segment - A code path segment to initialize.
125 * @returns {void}
126 */
127 "onCodePathSegmentStart": function(segment) {
128 if (!isInConstructor(funcInfo)) {
129 return;
130 }
131
132 // Initialize info.
133 segInfoMap[segment.id] = (
134 segment.prevSegments.length > 0 &&
135 segment.prevSegments.every(isCalled)
136 );
137 },
138
139 /**
140 * Reports if this is before `super()`.
141 * @param {ASTNode} node - A target node.
142 * @returns {void}
143 */
144 "ThisExpression": function(node) {
145 if (isBeforeCallOfSuper()) {
146 context.report({
147 message: "'this' is not allowed before 'super()'.",
148 node: node
149 });
150 }
151 },
152
153 /**
154 * Reports if this is before `super()`.
155 * @param {ASTNode} node - A target node.
156 * @returns {void}
157 */
158 "Super": function(node) {
159 if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
160 context.report({
161 message: "'super' is not allowed before 'super()'.",
162 node: node
163 });
164 }
165 },
166
167 /**
168 * Marks `super()` called.
169 * @param {ASTNode} node - A target node.
170 * @returns {void}
171 */
172 "CallExpression:exit": function(node) {
173 if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
174 var segments = funcInfo.codePath.currentSegments;
175 for (var i = 0; i < segments.length; ++i) {
176 segInfoMap[segments[i].id] = true;
177 }
178 }
179 },
180
181 /**
182 * Resets state.
183 * @returns {void}
184 */
185 "Program:exit": function() {
186 segInfoMap = Object.create(null);
187 }
188 };
189};
190
191module.exports.schema = [];