• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview A rule to disallow using `this`/`super` before `super()`.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18/**
19 * Checks whether or not a given node is a constructor.
20 * @param {ASTNode} node A node to check. This node type is one of
21 *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and
22 *   `ArrowFunctionExpression`.
23 * @returns {boolean} `true` if the node is a constructor.
24 */
25function isConstructorFunction(node) {
26    return (
27        node.type === "FunctionExpression" &&
28        node.parent.type === "MethodDefinition" &&
29        node.parent.kind === "constructor"
30    );
31}
32
33//------------------------------------------------------------------------------
34// Rule Definition
35//------------------------------------------------------------------------------
36
37module.exports = {
38    meta: {
39        type: "problem",
40
41        docs: {
42            description: "disallow `this`/`super` before calling `super()` in constructors",
43            category: "ECMAScript 6",
44            recommended: true,
45            url: "https://eslint.org/docs/rules/no-this-before-super"
46        },
47
48        schema: [],
49
50        messages: {
51            noBeforeSuper: "'{{kind}}' is not allowed before 'super()'."
52        }
53    },
54
55    create(context) {
56
57        /*
58         * Information for each constructor.
59         * - upper:      Information of the upper constructor.
60         * - hasExtends: A flag which shows whether the owner class has a valid
61         *   `extends` part.
62         * - scope:      The scope of the owner class.
63         * - codePath:   The code path of this constructor.
64         */
65        let funcInfo = null;
66
67        /*
68         * Information for each code path segment.
69         * Each key is the id of a code path segment.
70         * Each value is an object:
71         * - superCalled:  The flag which shows `super()` called in all code paths.
72         * - invalidNodes: The array of invalid ThisExpression and Super nodes.
73         */
74        let segInfoMap = Object.create(null);
75
76        /**
77         * Gets whether or not `super()` is called in a given code path segment.
78         * @param {CodePathSegment} segment A code path segment to get.
79         * @returns {boolean} `true` if `super()` is called.
80         */
81        function isCalled(segment) {
82            return !segment.reachable || segInfoMap[segment.id].superCalled;
83        }
84
85        /**
86         * Checks whether or not this is in a constructor.
87         * @returns {boolean} `true` if this is in a constructor.
88         */
89        function isInConstructorOfDerivedClass() {
90            return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
91        }
92
93        /**
94         * Checks whether or not this is before `super()` is called.
95         * @returns {boolean} `true` if this is before `super()` is called.
96         */
97        function isBeforeCallOfSuper() {
98            return (
99                isInConstructorOfDerivedClass() &&
100                !funcInfo.codePath.currentSegments.every(isCalled)
101            );
102        }
103
104        /**
105         * Sets a given node as invalid.
106         * @param {ASTNode} node A node to set as invalid. This is one of
107         *      a ThisExpression and a Super.
108         * @returns {void}
109         */
110        function setInvalid(node) {
111            const segments = funcInfo.codePath.currentSegments;
112
113            for (let i = 0; i < segments.length; ++i) {
114                const segment = segments[i];
115
116                if (segment.reachable) {
117                    segInfoMap[segment.id].invalidNodes.push(node);
118                }
119            }
120        }
121
122        /**
123         * Sets the current segment as `super` was called.
124         * @returns {void}
125         */
126        function setSuperCalled() {
127            const segments = funcInfo.codePath.currentSegments;
128
129            for (let i = 0; i < segments.length; ++i) {
130                const segment = segments[i];
131
132                if (segment.reachable) {
133                    segInfoMap[segment.id].superCalled = true;
134                }
135            }
136        }
137
138        return {
139
140            /**
141             * Adds information of a constructor into the stack.
142             * @param {CodePath} codePath A code path which was started.
143             * @param {ASTNode} node The current node.
144             * @returns {void}
145             */
146            onCodePathStart(codePath, node) {
147                if (isConstructorFunction(node)) {
148
149                    // Class > ClassBody > MethodDefinition > FunctionExpression
150                    const classNode = node.parent.parent.parent;
151
152                    funcInfo = {
153                        upper: funcInfo,
154                        isConstructor: true,
155                        hasExtends: Boolean(
156                            classNode.superClass &&
157                            !astUtils.isNullOrUndefined(classNode.superClass)
158                        ),
159                        codePath
160                    };
161                } else {
162                    funcInfo = {
163                        upper: funcInfo,
164                        isConstructor: false,
165                        hasExtends: false,
166                        codePath
167                    };
168                }
169            },
170
171            /**
172             * Removes the top of stack item.
173             *
174             * And this treverses all segments of this code path then reports every
175             * invalid node.
176             * @param {CodePath} codePath A code path which was ended.
177             * @returns {void}
178             */
179            onCodePathEnd(codePath) {
180                const isDerivedClass = funcInfo.hasExtends;
181
182                funcInfo = funcInfo.upper;
183                if (!isDerivedClass) {
184                    return;
185                }
186
187                codePath.traverseSegments((segment, controller) => {
188                    const info = segInfoMap[segment.id];
189
190                    for (let i = 0; i < info.invalidNodes.length; ++i) {
191                        const invalidNode = info.invalidNodes[i];
192
193                        context.report({
194                            messageId: "noBeforeSuper",
195                            node: invalidNode,
196                            data: {
197                                kind: invalidNode.type === "Super" ? "super" : "this"
198                            }
199                        });
200                    }
201
202                    if (info.superCalled) {
203                        controller.skip();
204                    }
205                });
206            },
207
208            /**
209             * Initialize information of a given code path segment.
210             * @param {CodePathSegment} segment A code path segment to initialize.
211             * @returns {void}
212             */
213            onCodePathSegmentStart(segment) {
214                if (!isInConstructorOfDerivedClass()) {
215                    return;
216                }
217
218                // Initialize info.
219                segInfoMap[segment.id] = {
220                    superCalled: (
221                        segment.prevSegments.length > 0 &&
222                        segment.prevSegments.every(isCalled)
223                    ),
224                    invalidNodes: []
225                };
226            },
227
228            /**
229             * Update information of the code path segment when a code path was
230             * looped.
231             * @param {CodePathSegment} fromSegment The code path segment of the
232             *      end of a loop.
233             * @param {CodePathSegment} toSegment A code path segment of the head
234             *      of a loop.
235             * @returns {void}
236             */
237            onCodePathSegmentLoop(fromSegment, toSegment) {
238                if (!isInConstructorOfDerivedClass()) {
239                    return;
240                }
241
242                // Update information inside of the loop.
243                funcInfo.codePath.traverseSegments(
244                    { first: toSegment, last: fromSegment },
245                    (segment, controller) => {
246                        const info = segInfoMap[segment.id];
247
248                        if (info.superCalled) {
249                            info.invalidNodes = [];
250                            controller.skip();
251                        } else if (
252                            segment.prevSegments.length > 0 &&
253                            segment.prevSegments.every(isCalled)
254                        ) {
255                            info.superCalled = true;
256                            info.invalidNodes = [];
257                        }
258                    }
259                );
260            },
261
262            /**
263             * Reports if this is before `super()`.
264             * @param {ASTNode} node A target node.
265             * @returns {void}
266             */
267            ThisExpression(node) {
268                if (isBeforeCallOfSuper()) {
269                    setInvalid(node);
270                }
271            },
272
273            /**
274             * Reports if this is before `super()`.
275             * @param {ASTNode} node A target node.
276             * @returns {void}
277             */
278            Super(node) {
279                if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
280                    setInvalid(node);
281                }
282            },
283
284            /**
285             * Marks `super()` called.
286             * @param {ASTNode} node A target node.
287             * @returns {void}
288             */
289            "CallExpression:exit"(node) {
290                if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
291                    setSuperCalled();
292                }
293            },
294
295            /**
296             * Resets state.
297             * @returns {void}
298             */
299            "Program:exit"() {
300                segInfoMap = Object.create(null);
301            }
302        };
303    }
304};
305