You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							395 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
	
	
							395 lines
						
					
					
						
							14 KiB
						
					
					
				/** | 
						|
 * @fileoverview A rule to verify `super()` callings in constructor. | 
						|
 * @author Toru Nagashima | 
						|
 */ | 
						|
 | 
						|
"use strict"; | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Helpers | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
/** | 
						|
 * Checks whether a given code path segment is reachable or not. | 
						|
 * @param {CodePathSegment} segment A code path segment to check. | 
						|
 * @returns {boolean} `true` if the segment is reachable. | 
						|
 */ | 
						|
function isReachable(segment) { | 
						|
    return segment.reachable; | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Checks whether or not a given node is a constructor. | 
						|
 * @param {ASTNode} node A node to check. This node type is one of | 
						|
 *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and | 
						|
 *   `ArrowFunctionExpression`. | 
						|
 * @returns {boolean} `true` if the node is a constructor. | 
						|
 */ | 
						|
function isConstructorFunction(node) { | 
						|
    return ( | 
						|
        node.type === "FunctionExpression" && | 
						|
        node.parent.type === "MethodDefinition" && | 
						|
        node.parent.kind === "constructor" | 
						|
    ); | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Checks whether a given node can be a constructor or not. | 
						|
 * @param {ASTNode} node A node to check. | 
						|
 * @returns {boolean} `true` if the node can be a constructor. | 
						|
 */ | 
						|
function isPossibleConstructor(node) { | 
						|
    if (!node) { | 
						|
        return false; | 
						|
    } | 
						|
 | 
						|
    switch (node.type) { | 
						|
        case "ClassExpression": | 
						|
        case "FunctionExpression": | 
						|
        case "ThisExpression": | 
						|
        case "MemberExpression": | 
						|
        case "CallExpression": | 
						|
        case "NewExpression": | 
						|
        case "YieldExpression": | 
						|
        case "TaggedTemplateExpression": | 
						|
        case "MetaProperty": | 
						|
            return true; | 
						|
 | 
						|
        case "Identifier": | 
						|
            return node.name !== "undefined"; | 
						|
 | 
						|
        case "AssignmentExpression": | 
						|
            return isPossibleConstructor(node.right); | 
						|
 | 
						|
        case "LogicalExpression": | 
						|
            return ( | 
						|
                isPossibleConstructor(node.left) || | 
						|
                isPossibleConstructor(node.right) | 
						|
            ); | 
						|
 | 
						|
        case "ConditionalExpression": | 
						|
            return ( | 
						|
                isPossibleConstructor(node.alternate) || | 
						|
                isPossibleConstructor(node.consequent) | 
						|
            ); | 
						|
 | 
						|
        case "SequenceExpression": { | 
						|
            const lastExpression = node.expressions[node.expressions.length - 1]; | 
						|
 | 
						|
            return isPossibleConstructor(lastExpression); | 
						|
        } | 
						|
 | 
						|
        default: | 
						|
            return false; | 
						|
    } | 
						|
} | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Rule Definition | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
module.exports = { | 
						|
    meta: { | 
						|
        type: "problem", | 
						|
 | 
						|
        docs: { | 
						|
            description: "require `super()` calls in constructors", | 
						|
            category: "ECMAScript 6", | 
						|
            recommended: true, | 
						|
            url: "https://eslint.org/docs/rules/constructor-super" | 
						|
        }, | 
						|
 | 
						|
        schema: [], | 
						|
 | 
						|
        messages: { | 
						|
            missingSome: "Lacked a call of 'super()' in some code paths.", | 
						|
            missingAll: "Expected to call 'super()'.", | 
						|
 | 
						|
            duplicate: "Unexpected duplicate 'super()'.", | 
						|
            badSuper: "Unexpected 'super()' because 'super' is not a constructor.", | 
						|
            unexpected: "Unexpected 'super()'." | 
						|
        } | 
						|
    }, | 
						|
 | 
						|
    create(context) { | 
						|
 | 
						|
        /* | 
						|
         * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} | 
						|
         * Information for each constructor. | 
						|
         * - upper:      Information of the upper constructor. | 
						|
         * - hasExtends: A flag which shows whether own class has a valid `extends` | 
						|
         *               part. | 
						|
         * - scope:      The scope of own class. | 
						|
         * - codePath:   The code path object of the constructor. | 
						|
         */ | 
						|
        let funcInfo = null; | 
						|
 | 
						|
        /* | 
						|
         * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>} | 
						|
         * Information for each code path segment. | 
						|
         * - calledInSomePaths:  A flag of be called `super()` in some code paths. | 
						|
         * - calledInEveryPaths: A flag of be called `super()` in all code paths. | 
						|
         * - validNodes: | 
						|
         */ | 
						|
        let segInfoMap = Object.create(null); | 
						|
 | 
						|
        /** | 
						|
         * Gets the flag which shows `super()` is called in some paths. | 
						|
         * @param {CodePathSegment} segment A code path segment to get. | 
						|
         * @returns {boolean} The flag which shows `super()` is called in some paths | 
						|
         */ | 
						|
        function isCalledInSomePath(segment) { | 
						|
            return segment.reachable && segInfoMap[segment.id].calledInSomePaths; | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Gets the flag which shows `super()` is called in all paths. | 
						|
         * @param {CodePathSegment} segment A code path segment to get. | 
						|
         * @returns {boolean} The flag which shows `super()` is called in all paths. | 
						|
         */ | 
						|
        function isCalledInEveryPath(segment) { | 
						|
 | 
						|
            /* | 
						|
             * If specific segment is the looped segment of the current segment, | 
						|
             * skip the segment. | 
						|
             * If not skipped, this never becomes true after a loop. | 
						|
             */ | 
						|
            if (segment.nextSegments.length === 1 && | 
						|
                segment.nextSegments[0].isLoopedPrevSegment(segment) | 
						|
            ) { | 
						|
                return true; | 
						|
            } | 
						|
            return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; | 
						|
        } | 
						|
 | 
						|
        return { | 
						|
 | 
						|
            /** | 
						|
             * Stacks a constructor information. | 
						|
             * @param {CodePath} codePath A code path which was started. | 
						|
             * @param {ASTNode} node The current node. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            onCodePathStart(codePath, node) { | 
						|
                if (isConstructorFunction(node)) { | 
						|
 | 
						|
                    // Class > ClassBody > MethodDefinition > FunctionExpression | 
						|
                    const classNode = node.parent.parent.parent; | 
						|
                    const superClass = classNode.superClass; | 
						|
 | 
						|
                    funcInfo = { | 
						|
                        upper: funcInfo, | 
						|
                        isConstructor: true, | 
						|
                        hasExtends: Boolean(superClass), | 
						|
                        superIsConstructor: isPossibleConstructor(superClass), | 
						|
                        codePath | 
						|
                    }; | 
						|
                } else { | 
						|
                    funcInfo = { | 
						|
                        upper: funcInfo, | 
						|
                        isConstructor: false, | 
						|
                        hasExtends: false, | 
						|
                        superIsConstructor: false, | 
						|
                        codePath | 
						|
                    }; | 
						|
                } | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Pops a constructor information. | 
						|
             * And reports if `super()` lacked. | 
						|
             * @param {CodePath} codePath A code path which was ended. | 
						|
             * @param {ASTNode} node The current node. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            onCodePathEnd(codePath, node) { | 
						|
                const hasExtends = funcInfo.hasExtends; | 
						|
 | 
						|
                // Pop. | 
						|
                funcInfo = funcInfo.upper; | 
						|
 | 
						|
                if (!hasExtends) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Reports if `super()` lacked. | 
						|
                const segments = codePath.returnedSegments; | 
						|
                const calledInEveryPaths = segments.every(isCalledInEveryPath); | 
						|
                const calledInSomePaths = segments.some(isCalledInSomePath); | 
						|
 | 
						|
                if (!calledInEveryPaths) { | 
						|
                    context.report({ | 
						|
                        messageId: calledInSomePaths | 
						|
                            ? "missingSome" | 
						|
                            : "missingAll", | 
						|
                        node: node.parent | 
						|
                    }); | 
						|
                } | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Initialize information of a given code path segment. | 
						|
             * @param {CodePathSegment} segment A code path segment to initialize. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            onCodePathSegmentStart(segment) { | 
						|
                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Initialize info. | 
						|
                const info = segInfoMap[segment.id] = { | 
						|
                    calledInSomePaths: false, | 
						|
                    calledInEveryPaths: false, | 
						|
                    validNodes: [] | 
						|
                }; | 
						|
 | 
						|
                // When there are previous segments, aggregates these. | 
						|
                const prevSegments = segment.prevSegments; | 
						|
 | 
						|
                if (prevSegments.length > 0) { | 
						|
                    info.calledInSomePaths = prevSegments.some(isCalledInSomePath); | 
						|
                    info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); | 
						|
                } | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Update information of the code path segment when a code path was | 
						|
             * looped. | 
						|
             * @param {CodePathSegment} fromSegment The code path segment of the | 
						|
             *      end of a loop. | 
						|
             * @param {CodePathSegment} toSegment A code path segment of the head | 
						|
             *      of a loop. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            onCodePathSegmentLoop(fromSegment, toSegment) { | 
						|
                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Update information inside of the loop. | 
						|
                const isRealLoop = toSegment.prevSegments.length >= 2; | 
						|
 | 
						|
                funcInfo.codePath.traverseSegments( | 
						|
                    { first: toSegment, last: fromSegment }, | 
						|
                    segment => { | 
						|
                        const info = segInfoMap[segment.id]; | 
						|
                        const prevSegments = segment.prevSegments; | 
						|
 | 
						|
                        // Updates flags. | 
						|
                        info.calledInSomePaths = prevSegments.some(isCalledInSomePath); | 
						|
                        info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); | 
						|
 | 
						|
                        // If flags become true anew, reports the valid nodes. | 
						|
                        if (info.calledInSomePaths || isRealLoop) { | 
						|
                            const nodes = info.validNodes; | 
						|
 | 
						|
                            info.validNodes = []; | 
						|
 | 
						|
                            for (let i = 0; i < nodes.length; ++i) { | 
						|
                                const node = nodes[i]; | 
						|
 | 
						|
                                context.report({ | 
						|
                                    messageId: "duplicate", | 
						|
                                    node | 
						|
                                }); | 
						|
                            } | 
						|
                        } | 
						|
                    } | 
						|
                ); | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Checks for a call of `super()`. | 
						|
             * @param {ASTNode} node A CallExpression node to check. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            "CallExpression:exit"(node) { | 
						|
                if (!(funcInfo && funcInfo.isConstructor)) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Skips except `super()`. | 
						|
                if (node.callee.type !== "Super") { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Reports if needed. | 
						|
                if (funcInfo.hasExtends) { | 
						|
                    const segments = funcInfo.codePath.currentSegments; | 
						|
                    let duplicate = false; | 
						|
                    let info = null; | 
						|
 | 
						|
                    for (let i = 0; i < segments.length; ++i) { | 
						|
                        const segment = segments[i]; | 
						|
 | 
						|
                        if (segment.reachable) { | 
						|
                            info = segInfoMap[segment.id]; | 
						|
 | 
						|
                            duplicate = duplicate || info.calledInSomePaths; | 
						|
                            info.calledInSomePaths = info.calledInEveryPaths = true; | 
						|
                        } | 
						|
                    } | 
						|
 | 
						|
                    if (info) { | 
						|
                        if (duplicate) { | 
						|
                            context.report({ | 
						|
                                messageId: "duplicate", | 
						|
                                node | 
						|
                            }); | 
						|
                        } else if (!funcInfo.superIsConstructor) { | 
						|
                            context.report({ | 
						|
                                messageId: "badSuper", | 
						|
                                node | 
						|
                            }); | 
						|
                        } else { | 
						|
                            info.validNodes.push(node); | 
						|
                        } | 
						|
                    } | 
						|
                } else if (funcInfo.codePath.currentSegments.some(isReachable)) { | 
						|
                    context.report({ | 
						|
                        messageId: "unexpected", | 
						|
                        node | 
						|
                    }); | 
						|
                } | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Set the mark to the returned path as `super()` was called. | 
						|
             * @param {ASTNode} node A ReturnStatement node to check. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            ReturnStatement(node) { | 
						|
                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Skips if no argument. | 
						|
                if (!node.argument) { | 
						|
                    return; | 
						|
                } | 
						|
 | 
						|
                // Returning argument is a substitute of 'super()'. | 
						|
                const segments = funcInfo.codePath.currentSegments; | 
						|
 | 
						|
                for (let i = 0; i < segments.length; ++i) { | 
						|
                    const segment = segments[i]; | 
						|
 | 
						|
                    if (segment.reachable) { | 
						|
                        const info = segInfoMap[segment.id]; | 
						|
 | 
						|
                        info.calledInSomePaths = info.calledInEveryPaths = true; | 
						|
                    } | 
						|
                } | 
						|
            }, | 
						|
 | 
						|
            /** | 
						|
             * Resets state. | 
						|
             * @returns {void} | 
						|
             */ | 
						|
            "Program:exit"() { | 
						|
                segInfoMap = Object.create(null); | 
						|
            } | 
						|
        }; | 
						|
    } | 
						|
};
 | 
						|
 |