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 | ;
|
8 |
|
9 | //------------------------------------------------------------------------------
|
10 | // Requirements
|
11 | //------------------------------------------------------------------------------
|
12 |
|
13 | var 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 | */
|
26 | function 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 |
|
38 | module.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 |
|
191 | module.exports.schema = [];
|