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