• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "extendsInterfaceBecomesImplements";
4    const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code];
5    registerCodeFix({
6        errorCodes,
7        getCodeActions(context) {
8            const { sourceFile } = context;
9            const nodes = getNodes(sourceFile, context.span.start);
10            if (!nodes) return undefined;
11            const { extendsToken, heritageClauses } = nodes;
12            const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses));
13            return [createCodeFixAction(fixId, changes, Diagnostics.Change_extends_to_implements, fixId, Diagnostics.Change_all_extended_interfaces_to_implements)];
14        },
15        fixIds: [fixId],
16        getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
17            const nodes = getNodes(diag.file, diag.start);
18            if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses);
19        }),
20    });
21
22    function getNodes(sourceFile: SourceFile, pos: number) {
23        const token = getTokenAtPosition(sourceFile, pos);
24        const heritageClauses = getContainingClass(token)!.heritageClauses!;
25        const extendsToken = heritageClauses[0].getFirstToken()!;
26        return extendsToken.kind === SyntaxKind.ExtendsKeyword ? { extendsToken, heritageClauses } : undefined;
27    }
28
29    function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: readonly HeritageClause[]): void {
30        changes.replaceNode(sourceFile, extendsToken, factory.createToken(SyntaxKind.ImplementsKeyword));
31
32        // If there is already an implements clause, replace the implements keyword with a comma.
33        if (heritageClauses.length === 2 &&
34            heritageClauses[0].token === SyntaxKind.ExtendsKeyword &&
35            heritageClauses[1].token === SyntaxKind.ImplementsKeyword) {
36
37            const implementsToken = heritageClauses[1].getFirstToken()!;
38            const implementsFullStart = implementsToken.getFullStart();
39            changes.replaceRange(sourceFile, { pos: implementsFullStart, end: implementsFullStart }, factory.createToken(SyntaxKind.CommaToken));
40
41            // Rough heuristic: delete trailing whitespace after keyword so that it's not excessive.
42            // (Trailing because leading might be indentation, which is more sensitive.)
43            const text = sourceFile.text;
44            let end = implementsToken.end;
45            while (end < text.length && isWhiteSpaceSingleLine(text.charCodeAt(end))) {
46                end++;
47            }
48
49            changes.deleteRange(sourceFile, { pos: implementsToken.getStart(), end });
50        }
51    }
52}
53