• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "classSuperMustPrecedeThisAccess";
4    const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code];
5    registerCodeFix({
6        errorCodes,
7        getCodeActions(context) {
8            const { sourceFile, span } = context;
9            const nodes = getNodes(sourceFile, span.start);
10            if (!nodes) return undefined;
11            const { constructor, superCall } = nodes;
12            const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall));
13            return [createCodeFixAction(fixId, changes, Diagnostics.Make_super_call_the_first_statement_in_the_constructor, fixId, Diagnostics.Make_all_super_calls_the_first_statement_in_their_constructor)];
14        },
15        fixIds: [fixId],
16        getAllCodeActions(context) {
17            const { sourceFile } = context;
18            const seenClasses = new Map<string, true>(); // Ensure we only do this once per class.
19            return codeFixAll(context, errorCodes, (changes, diag) => {
20                const nodes = getNodes(diag.file, diag.start);
21                if (!nodes) return;
22                const { constructor, superCall } = nodes;
23                if (addToSeen(seenClasses, getNodeId(constructor.parent))) {
24                    doChange(changes, sourceFile, constructor, superCall);
25                }
26            });
27        },
28    });
29
30    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, constructor: ConstructorDeclaration, superCall: ExpressionStatement): void {
31        changes.insertNodeAtConstructorStart(sourceFile, constructor, superCall);
32        changes.delete(sourceFile, superCall);
33    }
34
35    function getNodes(sourceFile: SourceFile, pos: number): { readonly constructor: ConstructorDeclaration, readonly superCall: ExpressionStatement } | undefined {
36        const token = getTokenAtPosition(sourceFile, pos);
37        if (token.kind !== SyntaxKind.ThisKeyword) return undefined;
38        const constructor = getContainingFunction(token) as ConstructorDeclaration;
39        const superCall = findSuperCall(constructor.body!);
40        // figure out if the `this` access is actually inside the supercall
41        // i.e. super(this.a), since in that case we won't suggest a fix
42        return superCall && !superCall.expression.arguments.some(arg => isPropertyAccessExpression(arg) && arg.expression === token) ? { constructor, superCall } : undefined;
43    }
44
45    function findSuperCall(n: Node): ExpressionStatement & { expression: CallExpression } | undefined {
46        return isExpressionStatement(n) && isSuperCall(n.expression)
47            ? n as ExpressionStatement & { expression: CallExpression }
48            : isFunctionLike(n)
49                ? undefined
50                : forEachChild(n, findSuperCall);
51    }
52}
53