From 5c70641d3c97204fb571c9771b2fcfb3e591509c Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 13 Nov 2017 11:00:06 -0800 Subject: [PATCH 01/11] Support a "getCombinedCodeFix" service --- src/compiler/core.ts | 17 +- src/compiler/types.ts | 12 +- src/harness/fourslash.ts | 44 +- src/harness/harnessLanguageService.ts | 3 +- src/server/client.ts | 21 +- src/server/protocol.ts | 26 +- src/server/session.ts | 36 +- src/services/codeFixProvider.ts | 73 +++- .../addMissingInvocationForDecorator.ts | 32 +- ...correctQualifiedNameToIndexedAccessType.ts | 51 ++- .../codefixes/disableJsDiagnostics.ts | 78 ++-- src/services/codefixes/fixAddMissingMember.ts | 324 +++++++------- src/services/codefixes/fixCannotFindModule.ts | 49 ++- ...sDoesntImplementInheritedAbstractMember.ts | 75 ++-- .../fixClassIncorrectlyImplementsInterface.ts | 104 ++--- .../fixClassSuperMustPrecedeThisAccess.ts | 87 ++-- .../fixConstructorForDerivedNeedSuperCall.ts | 41 +- .../fixExtendsInterfaceBecomesImplements.ts | 66 ++- .../fixForgottenThisPropertyAccess.ts | 36 +- src/services/codefixes/fixJSDocTypes.ts | 130 +++--- src/services/codefixes/fixSpelling.ts | 49 ++- src/services/codefixes/fixUnusedIdentifier.ts | 385 +++++++++-------- src/services/codefixes/helpers.ts | 130 ++---- src/services/codefixes/importFixes.ts | 9 +- src/services/codefixes/inferFromUsage.ts | 407 +++++++++--------- src/services/services.ts | 12 +- src/services/textChanges.ts | 22 +- src/services/types.ts | 13 +- src/services/utilities.ts | 26 ++ .../reference/api/tsserverlibrary.d.ts | 43 +- tests/baselines/reference/api/typescript.d.ts | 23 +- ...eFixAddMissingInvocationForDecorator01.ts} | 0 ...FixAddMissingInvocationForDecorator_all.ts | 23 + .../fourslash/codeFixAddMissingMember_all.ts | 26 ++ .../codeFixAddMissingMember_all_js.ts | 32 ++ .../fourslash/codeFixCannotFindModule_all.ts | 31 ++ .../fourslash/codeFixChangeJSDocSyntax_all.ts | 7 + .../codeFixChangeJSDocSyntax_all_nullable.ts | 7 + .../codeFixClassExtendAbstractMethod_all.ts | 24 ++ .../codeFixClassImplementInterface_all.ts | 25 ++ ...codeFixClassSuperMustPrecedeThisAccess.ts} | 0 ...eFixClassSuperMustPrecedeThisAccess_all.ts | 33 ++ ...stPrecedeThisAccess_callWithThisInside.ts} | 0 ...eFixConstructorForDerivedNeedSuperCall.ts} | 0 ...xConstructorForDerivedNeedSuperCall_all.ts | 21 + ...rectQualifiedNameToIndexedAccessType01.ts} | 0 ...ectQualifiedNameToIndexedAccessType_all.ts | 17 + .../codeFixDisableJsDiagnosticsInFile_all.ts | 20 + ...deFixExtendsInterfaceBecomesImplements.ts} | 0 ...ixExtendsInterfaceBecomesImplements_all.ts | 12 + ...> codeFixForgottenThisPropertyAccess01.ts} | 0 ...> codeFixForgottenThisPropertyAccess02.ts} | 0 .../codeFixForgottenThisPropertyAccess_all.ts | 21 + .../fourslash/codeFixInferFromUsage_all.ts | 25 ++ ...orrectSpelling1.ts => codeFixSpelling1.ts} | 0 ...orrectSpelling2.ts => codeFixSpelling2.ts} | 0 ...orrectSpelling3.ts => codeFixSpelling3.ts} | 0 ...orrectSpelling4.ts => codeFixSpelling4.ts} | 0 tests/cases/fourslash/codeFixSpelling_all.ts | 15 + .../codeFixUnusedIdentifier_all_delete.ts | 15 + .../codeFixUnusedIdentifier_all_prefix.ts | 16 + tests/cases/fourslash/fourslash.ts | 3 +- ...sedParameterInConstructor1AddUnderscore.ts | 11 +- ...unusedParameterInFunction1AddUnderscore.ts | 5 +- .../unusedParameterInLambda1AddUnderscore.ts | 7 +- ...unusedVariableInForLoop5FSAddUnderscore.ts | 8 +- ...unusedVariableInForLoop6FSAddUnderscore.ts | 9 +- 67 files changed, 1720 insertions(+), 1117 deletions(-) rename tests/cases/fourslash/{codeFixAddForgottenDecoratorCall01.ts => codeFixAddMissingInvocationForDecorator01.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts create mode 100644 tests/cases/fourslash/codeFixAddMissingMember_all.ts create mode 100644 tests/cases/fourslash/codeFixAddMissingMember_all_js.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_all.ts create mode 100644 tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts create mode 100644 tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts create mode 100644 tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterface_all.ts rename tests/cases/fourslash/{codeFixSuperAfterThis.ts => codeFixClassSuperMustPrecedeThisAccess.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts rename tests/cases/fourslash/{codeFixSuperCallWithThisInside.ts => codeFixClassSuperMustPrecedeThisAccess_callWithThisInside.ts} (100%) rename tests/cases/fourslash/{codeFixSuperCall.ts => codeFixConstructorForDerivedNeedSuperCall.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts rename tests/cases/fourslash/{codeFixReplaceQualifiedNameWithIndexedAccessType01.ts => codeFixCorrectQualifiedNameToIndexedAccessType01.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts create mode 100644 tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts rename tests/cases/fourslash/{codeFixChangeExtendsToImplements.ts => codeFixExtendsInterfaceBecomesImplements.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts rename tests/cases/fourslash/{codeFixAddForgottenThis01.ts => codeFixForgottenThisPropertyAccess01.ts} (100%) rename tests/cases/fourslash/{codeFixAddForgottenThis02.ts => codeFixForgottenThisPropertyAccess02.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsage_all.ts rename tests/cases/fourslash/{codeFixCorrectSpelling1.ts => codeFixSpelling1.ts} (100%) rename tests/cases/fourslash/{codeFixCorrectSpelling2.ts => codeFixSpelling2.ts} (100%) rename tests/cases/fourslash/{codeFixCorrectSpelling3.ts => codeFixSpelling3.ts} (100%) rename tests/cases/fourslash/{codeFixCorrectSpelling4.ts => codeFixSpelling4.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixSpelling_all.ts create mode 100644 tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts create mode 100644 tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fdf888b0cfcb0..519696c80d50a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -215,13 +215,27 @@ namespace ts { export function zipWith(arrayA: ReadonlyArray, arrayB: ReadonlyArray, callback: (a: T, b: U, index: number) => V): V[] { const result: V[] = []; - Debug.assert(arrayA.length === arrayB.length); + Debug.assertEqual(arrayA.length, arrayB.length); for (let i = 0; i < arrayA.length; i++) { result.push(callback(arrayA[i], arrayB[i], i)); } return result; } + export function zipToIterator(arrayA: ReadonlyArray, arrayB: ReadonlyArray): Iterator<[T, U]> { + Debug.assertEqual(arrayA.length, arrayB.length); + let i = 0; + return { + next() { + if (i === arrayA.length) { + return { value: undefined as never, done: true }; + } + i++; + return { value: [arrayA[i - 1], arrayB[i - 1]], done: false }; + } + }; + } + export function zipToMap(keys: ReadonlyArray, values: ReadonlyArray): Map { Debug.assert(keys.length === values.length); const map = createMap(); @@ -1345,7 +1359,6 @@ namespace ts { this.set(key, values = [value]); } return values; - } function multiMapRemove(this: MultiMap, key: string, value: T) { const values = this.get(key); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c6d1c1327becd..371c819f6a5c6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -949,7 +949,7 @@ namespace ts { export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; /* @internal */ returnFlowNode?: FlowNode; } @@ -957,14 +957,14 @@ namespace ts { /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ export interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } // See the comment on MethodDeclaration for the intuition behind GetAccessorDeclaration being a // ClassElement and an ObjectLiteralElement. export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } @@ -973,7 +973,7 @@ namespace ts { // ClassElement and an ObjectLiteralElement. export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } @@ -982,7 +982,7 @@ namespace ts { export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } export interface TypeNode extends Node { @@ -1986,7 +1986,7 @@ namespace ts { export interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cd992464a6558..d5d0cb44b41f8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2371,7 +2371,7 @@ Actual: ${stringify(fullActual)}`); */ public getAndApplyCodeActions(errorCode?: number, index?: number) { const fileName = this.activeFile.fileName; - this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index); + this.applyCodeActions(this.getCodeFixes(fileName, errorCode), index); } public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) { @@ -2424,6 +2424,16 @@ Actual: ${stringify(fullActual)}`); this.verifyRangeIs(expectedText, includeWhiteSpace); } + public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void { + const { groupId, newFileContent } = options; + const groupIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.groupId); + ts.Debug.assert(ts.contains(groupIds, groupId), "No available code fix has that group id.", () => `Expected '${groupId}'. Available group ids: ${groupIds}`); + const { changes, commands } = this.languageService.getCombinedCodeFix(this.activeFile.fileName, groupId, this.formatCodeSettings); + assert.deepEqual(commands, options.commands); + this.applyChanges(changes); + this.verifyCurrentFileContent(newFileContent); + } + /** * Applies fixes for the errors in fileName and compares the results to * expectedContents after all fixes have been applied. @@ -2436,7 +2446,7 @@ Actual: ${stringify(fullActual)}`); public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) { fileName = fileName ? fileName : this.activeFile.fileName; - this.applyCodeActions(this.getCodeFixActions(fileName)); + this.applyCodeActions(this.getCodeFixes(fileName)); const actualContents: string = this.getFileContent(fileName); if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) { @@ -2446,7 +2456,7 @@ Actual: ${stringify(fullActual)}`); public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) { const fileName = this.activeFile.fileName; - const actions = this.getCodeFixActions(fileName, options.errorCode); + const actions = this.getCodeFixes(fileName, options.errorCode); let index = options.index; if (index === undefined) { if (!(actions && actions.length === 1)) { @@ -2472,8 +2482,8 @@ Actual: ${stringify(fullActual)}`); } private verifyNewContent(options: FourSlashInterface.NewContentOptions) { - if (options.newFileContent) { - assert(!options.newRangeContent); + if (options.newFileContent !== undefined) { + assert(options.newRangeContent === undefined); this.verifyCurrentFileContent(options.newFileContent); } else { @@ -2485,7 +2495,7 @@ Actual: ${stringify(fullActual)}`); * Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found. * @param fileName Path to file where error should be retrieved from. */ - private getCodeFixActions(fileName: string, errorCode?: number): ts.CodeAction[] { + private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFix[] { const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({ start: diagnostic.start, length: diagnostic.length, @@ -2501,7 +2511,7 @@ Actual: ${stringify(fullActual)}`); }); } - private applyCodeActions(actions: ts.CodeAction[], index?: number): void { + private applyCodeActions(actions: ReadonlyArray, index?: number): void { if (index === undefined) { if (!(actions && actions.length === 1)) { this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`); @@ -2514,8 +2524,10 @@ Actual: ${stringify(fullActual)}`); } } - const changes = actions[index].changes; + this.applyChanges(actions[index].changes); + } + private applyChanges(changes: ReadonlyArray): void { for (const change of changes) { this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); } @@ -2527,7 +2539,7 @@ Actual: ${stringify(fullActual)}`); this.raiseError("At least one range should be specified in the testfile."); } - const codeFixes = this.getCodeFixActions(this.activeFile.fileName, errorCode); + const codeFixes = this.getCodeFixes(this.activeFile.fileName, errorCode); if (codeFixes.length === 0) { if (expectedTextArray.length !== 0) { @@ -2866,7 +2878,7 @@ Actual: ${stringify(fullActual)}`); } public verifyCodeFixAvailable(negative: boolean, info: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined) { - const codeFixes = this.getCodeFixActions(this.activeFile.fileName); + const codeFixes = this.getCodeFixes(this.activeFile.fileName); if (negative) { if (codeFixes.length) { @@ -3033,7 +3045,7 @@ Actual: ${stringify(fullActual)}`); } public printAvailableCodeFixes() { - const codeFixes = this.getCodeFixActions(this.activeFile.fileName); + const codeFixes = this.getCodeFixes(this.activeFile.fileName); Harness.IO.log(stringify(codeFixes)); } @@ -4143,6 +4155,10 @@ namespace FourSlashInterface { this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index); } + public codeFixAll(options: VerifyCodeFixAllOptions): void { + this.state.verifyCodeFixAll(options); + } + public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: ts.FormatCodeSettings): void { this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, actionName, formattingOptions); } @@ -4578,6 +4594,12 @@ namespace FourSlashInterface { commands?: ts.CodeActionCommand[]; } + export interface VerifyCodeFixAllOptions { + groupId: string; + newFileContent: string; + commands: ReadonlyArray<{}>; + } + export interface VerifyRefactorOptions { name: string; actionName: string; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d4b5a9c2a16db..6724b8a0d3fb6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -503,9 +503,10 @@ namespace Harness.LanguageService { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); } - getCodeFixesAtPosition(): ts.CodeAction[] { + getCodeFixesAtPosition(): never { throw new Error("Not supported on the shim."); } + getCombinedCodeFix = ts.notImplemented; applyCodeActionCommand = ts.notImplemented; getCodeFixDiagnostics(): ts.Diagnostic[] { throw new Error("Not supported on the shim."); diff --git a/src/server/client.ts b/src/server/client.ts index 8286119e8289e..2b44a82b8e71c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -199,7 +199,7 @@ namespace ts.server { const response = this.processResponse(request); Debug.assert(response.body.length === 1, "Unexpected length of completion details response body."); - const convertedCodeActions = map(response.body[0].codeActions, codeAction => this.convertCodeActions(codeAction, fileName)); + const convertedCodeActions = map(response.body[0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); return { ...response.body[0], codeActions: convertedCodeActions }; } @@ -552,15 +552,17 @@ namespace ts.server { return notImplemented(); } - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] { + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeFix[] { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - return response.body.map(entry => this.convertCodeActions(entry, file)); + return response.body.map(({ description, changes, groupId }) => ({ description, changes: this.convertChanges(changes, file), groupId })); } + getCombinedCodeFix = notImplemented; + applyCodeActionCommand = notImplemented; private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { @@ -637,14 +639,11 @@ namespace ts.server { }); } - convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction { - return { - description: entry.description, - changes: entry.changes.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) - })) - }; + private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { + return changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) + })); } convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6e96365a4ac11..0246f532cada4 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -99,9 +99,12 @@ namespace ts.server.protocol { BreakpointStatement = "breakpointStatement", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", - ApplyCodeActionCommand = "applyCodeActionCommand", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", + GetCombinedCodeFix = "getCombinedCodeFix", + /* @internal */ + GetCombinedCodeFixFull = "getCombinedCodeFix", + ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -533,6 +536,11 @@ namespace ts.server.protocol { arguments: CodeFixRequestArgs; } + export interface GetCombinedCodeFixRequest extends Request { + command: CommandTypes.GetCombinedCodeFix; + arguments: GetCombinedCodeFixRequestArgs; + } + export interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -585,6 +593,10 @@ namespace ts.server.protocol { errorCodes?: number[]; } + export interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { + groupId: {}; + } + export interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; @@ -1568,7 +1580,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeFix[]; } export interface CodeAction { @@ -1580,6 +1592,16 @@ namespace ts.server.protocol { commands?: {}[]; } + export interface CodeActionAll { + changes: FileCodeEdits[]; + commands: {}[] | undefined; + } + + export interface CodeFix extends CodeAction { + /** If present, one may call 'getAllCodeFixesInGroup' with this groupId. */ + groupId: {} | undefined; + } + /** * Format and format on key response message. */ diff --git a/src/server/session.ts b/src/server/session.ts index 309a6aef0cab4..2f19bc731714c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1538,11 +1538,7 @@ namespace ts.server { const oldText = snapshot.getText(0, snapshot.getLength()); mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits); } - return { - renameLocation: mappedRenameLocation, - renameFilename, - edits: edits.map(change => this.mapTextChangesToCodeEdits(project, change)) - }; + return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(project, edits) }; } else { return result; @@ -1571,6 +1567,18 @@ namespace ts.server { } } + private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeActionAll | CodeActionAll { + const { file, project } = this.getFileAndProject(args); + const formatOptions = this.projectService.getFormatCodeOptions(file); + const res = project.getLanguageService().getCombinedCodeFix(file, args.groupId, formatOptions); + if (simplifiedResult) { + return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; + } + else { + return res; + } + } + private applyCodeActionCommand(commandName: string, requestSeq: number, args: protocol.ApplyCodeActionCommandRequestArgs): void { const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them. for (const command of toArray(commands)) { @@ -1605,15 +1613,15 @@ namespace ts.server { } private mapCodeAction({ description, changes: unmappedChanges, commands }: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction { - const changes = unmappedChanges.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) - })); + const changes = unmappedChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, scriptInfo)); return { description, changes, commands }; } - private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits { - const scriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(textChanges.fileName)); + private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges[]): protocol.FileCodeEdits[] { + return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName)))); + } + + private mapTextChangesToCodeEditsUsingScriptinfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo): protocol.FileCodeEdits { return { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) @@ -1956,6 +1964,12 @@ namespace ts.server { [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => { return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.GetCombinedCodeFix]: (request: protocol.GetCombinedCodeFixRequest) => { + return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.GetCombinedCodeFixFull]: (request: protocol.GetCombinedCodeFixRequest) => { + return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => { this.applyCodeActionCommand(request.command, request.seq, request.arguments); return this.notRequired(); // Response will come asynchronously. diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index ad9ab520dabcf..12c7a84af5e7d 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -1,40 +1,56 @@ /* @internal */ namespace ts { - export interface CodeFix { + export interface CodeFixRegistration { errorCodes: number[]; - getCodeActions(context: CodeFixContext): CodeAction[] | undefined; + getCodeActions(context: CodeFixContext): CodeFix[] | undefined; + groupIds: string[]; + fixAllInGroup(context: CodeFixAllContext): CodeActionAll; } - export interface CodeFixContext extends textChanges.TextChangesContext { - errorCode: number; + export interface CodeFixContextBase extends textChanges.TextChangesContext { sourceFile: SourceFile; - span: TextSpan; program: Program; host: LanguageServiceHost; cancellationToken: CancellationToken; } + export interface CodeFixAllContext extends CodeFixContextBase { + groupId: {}; + } + + export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; + } + export namespace codefix { - const codeFixes: CodeFix[][] = []; + const codeFixes: CodeFixRegistration[][] = []; + const groups = createMap(); - export function registerCodeFix(codeFix: CodeFix) { - forEach(codeFix.errorCodes, error => { + export function registerCodeFix(codeFix: CodeFixRegistration) { + for (const error of codeFix.errorCodes) { let fixes = codeFixes[error]; if (!fixes) { fixes = []; codeFixes[error] = fixes; } fixes.push(codeFix); - }); + } + if (codeFix.groupIds) { + for (const gid of codeFix.groupIds) { + Debug.assert(!groups.has(gid)); + groups.set(gid, codeFix); + } + } } export function getSupportedErrorCodes() { return Object.keys(codeFixes); } - export function getFixes(context: CodeFixContext): CodeAction[] { + export function getFixes(context: CodeFixContext): CodeFix[] { const fixes = codeFixes[context.errorCode]; - const allActions: CodeAction[] = []; + const allActions: CodeFix[] = []; forEach(fixes, f => { const actions = f.getCodeActions(context); @@ -52,5 +68,40 @@ namespace ts { return allActions; } + + export function getAllFixes(context: CodeFixAllContext): CodeActionAll { + // Currently groupId is always a string. + return groups.get(cast(context.groupId, isString)).fixAllInGroup!(context); + } + + function createCodeActionAll(changes: FileTextChanges[], commands?: CodeActionCommand[]): CodeActionAll { + return { changes, commands }; + } + + export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges { + return { fileName, textChanges }; + } + + export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CodeActionAll { + const commands: CodeActionCommand[] = []; + const changes = textChanges.ChangeTracker.with(context, t => + eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); + return createCodeActionAll(changes, commands.length === 0 ? undefined : commands); + } + + export function codeFixAllWithTextChanges(context: CodeFixAllContext, errorCodes: number[], use: (changes: Push, error: Diagnostic) => void): CodeActionAll { + const changes: TextChange[] = []; + eachDiagnostic(context, errorCodes, diag => use(changes, diag)); + changes.sort((a, b) => b.span.start - a.span.start); + return createCodeActionAll([createFileTextChanges(context.sourceFile.fileName, changes)]); + } + + function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: Diagnostic) => void): void { + for (const diag of program.getSemanticDiagnostics(sourceFile)) { + if (contains(errorCodes, diag.code)) { + cb(diag); + } + } + } } } diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index 7f17aab6db2a8..3977ccab28e07 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -1,20 +1,22 @@ /* @internal */ namespace ts.codefix { + const groupId = "addMissingInvocationForDecorator"; + const errorCodes = [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code]; registerCodeFix({ - errorCodes: [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - const decorator = getAncestor(token, SyntaxKind.Decorator) as Decorator; - Debug.assert(!!decorator, "Expected position to be owned by a decorator."); - const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined); - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, decorator.expression, replacement); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), - changes: changeTracker.getChanges() - }]; - } + errorCodes, + getCodeActions: (context) => { + const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)), }); + + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const decorator = findAncestor(token, isDecorator)!; + Debug.assert(!!decorator, "Expected position to be owned by a decorator."); + const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined); + changeTracker.replaceNode(sourceFile, decorator.expression, replacement); + } } diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index e18190d6f6ac7..e7ffe7ed56e1b 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -1,27 +1,36 @@ /* @internal */ namespace ts.codefix { + const groupId = "correctQualifiedNameToIndexedAccessType"; + const errorCodes = [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - const qualifiedName = getAncestor(token, SyntaxKind.QualifiedName) as QualifiedName; - Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name."); - if (!isIdentifier(qualifiedName.left)) { - return undefined; + errorCodes, + getCodeActions(context) { + const qualifiedName = getQualifiedName(context.sourceFile, context.span.start); + if (!qualifiedName) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); + return [{ description, changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: (context) => codeFixAll(context, errorCodes, (changes, diag) => { + const q = getQualifiedName(diag.file, diag.start); + if (q) { + doChange(changes, diag.file, q); } - const leftText = qualifiedName.left.getText(sourceFile); - const rightText = qualifiedName.right.getText(sourceFile); - const replacement = createIndexedAccessTypeNode( - createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined), - createLiteralTypeNode(createLiteral(rightText))); - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, qualifiedName, replacement); - - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${leftText}["${rightText}"]`]), - changes: changeTracker.getChanges() - }]; - } + }), }); + + function getQualifiedName(sourceFile: SourceFile, pos: number): QualifiedName & { left: Identifier } | undefined { + const qualifiedName = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isQualifiedName)!; + Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name."); + return isIdentifier(qualifiedName.left) ? qualifiedName as QualifiedName & { left: Identifier } : undefined; + } + + function doChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, qualifiedName: QualifiedName): void { + const rightText = qualifiedName.right.text; + const replacement = createIndexedAccessTypeNode( + createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined), + createLiteralTypeNode(createLiteral(rightText))); + changeTracker.replaceNode(sourceFile, qualifiedName, replacement); + } } diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 291dc61c32f74..a1f642ab57e7b 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -1,18 +1,47 @@ /* @internal */ namespace ts.codefix { - registerCodeFix({ - errorCodes: getApplicableDiagnosticCodes(), - getCodeActions: getDisableJsDiagnosticsCodeActions + const groupId = "disableJsDiagnostics"; + const errorCodes = mapDefined(Object.keys(Diagnostics), key => { + const diag = (Diagnostics as MapLike)[key]; + return diag.category === DiagnosticCategory.Error ? diag.code : undefined; }); - function getApplicableDiagnosticCodes(): number[] { - const allDiagnostcs = >Diagnostics; - return Object.keys(allDiagnostcs) - .filter(d => allDiagnostcs[d] && allDiagnostcs[d].category === DiagnosticCategory.Error) - .map(d => allDiagnostcs[d].code); - } + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const { sourceFile, program, newLineCharacter, span } = context; - function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string) { + if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { + return undefined; + } + + return [{ + description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), + changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], + groupId, + }, + { + description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), + changes: [createFileTextChanges(sourceFile.fileName, [{ + span: { + start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0, + length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0 + }, + newText: `// @ts-nocheck${newLineCharacter}` + }])], + // groupId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. + groupId: undefined, + }]; + }, + groupIds: [groupId], // No point applying as a group, doing it once will fix all errors + fixAllInGroup: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + if (err.start !== undefined) { + changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); + } + }), + }); + + function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): TextChange { const { line } = getLineAndCharacterOfPosition(sourceFile, position); const lineStartPosition = getStartPositionOfLine(line, sourceFile); const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); @@ -38,33 +67,4 @@ namespace ts.codefix { newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}` }; } - - function getDisableJsDiagnosticsCodeActions(context: CodeFixContext): CodeAction[] | undefined { - const { sourceFile, program, newLineCharacter, span } = context; - - if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { - return undefined; - } - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)] - }] - }, - { - description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { - start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0, - length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0 - }, - newText: `// @ts-nocheck${newLineCharacter}` - }] - }] - }]; - } } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 19bd592b7a7d6..3287597bb7aba 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,209 +1,195 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [ + Diagnostics.Property_0_does_not_exist_on_type_1.code, + Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, + ]; + const groupId = "addMissingMember"; registerCodeFix({ - errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code, - Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code], - getCodeActions: getActionsForAddMissingMember - }); + errorCodes, + getCodeActions(context) { + const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker()); + if (!info) return undefined; + const { classDeclaration, classDeclarationSourceFile, classOpenBrace, inJs, makeStatic, token, call } = info; + const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classOpenBrace, token, call, makeStatic, inJs); + const addMember = inJs ? + singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) : + getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classOpenBrace, token, classDeclaration, makeStatic); + return concatenate(singleElementArray(methodCodeAction), addMember); + }, + groupIds: [groupId], + fixAllInGroup: context => { + const seenNames = createMap(); + return codeFixAll(context, errorCodes, (changes, diag) => { + const { newLineCharacter, program } = context; + const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); + if (!info) return; + const { classDeclaration, classDeclarationSourceFile, classOpenBrace, inJs, makeStatic, token, call } = info; + if (!addToSeenStrings(seenNames, token.text)) { + return; + } - function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined { + // Always prefer to add a method declaration if possible. + if (call) { + addMethodDeclaration(changes, classDeclarationSourceFile, classOpenBrace, token, call, newLineCharacter, makeStatic, inJs); + } + else { + if (inJs) { + addMissingMemberInJs(changes, classDeclarationSourceFile, classDeclaration, token.text, makeStatic, newLineCharacter); + } + else { + const typeNode = getTypeNode(program.getTypeChecker(), classDeclaration, token); + addPropertyDeclaration(changes, classDeclarationSourceFile, classOpenBrace, token.text, typeNode, makeStatic, newLineCharacter); + } + } + }); + }, + }); - const tokenSourceFile = context.sourceFile; - const start = context.span.start; + interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; classOpenBrace: Node; inJs: boolean; call: CallExpression; } + function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined { // The identifier of the missing property. eg: // this.missing = 1; // ^^^^^^^ - const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false); - - if (token.kind !== SyntaxKind.Identifier) { + const token = getTokenAtPosition(tokenSourceFile, tokenPos, /*includeJsDocComment*/ false); + if (!isIdentifier(token)) { return undefined; } - if (!isPropertyAccessExpression(token.parent)) { + const classAndMakeStatic = getClassAndMakeStatic(token, checker); + if (!classAndMakeStatic) { return undefined; } + const { classDeclaration, makeStatic } = classAndMakeStatic; + const classDeclarationSourceFile = classDeclaration.getSourceFile(); + const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile); + const inJs = isInJavaScriptFile(classDeclarationSourceFile); + const call = tryCast(token.parent.parent, isCallExpression); - const tokenName = token.getText(tokenSourceFile); + return { token, classDeclaration, makeStatic, classDeclarationSourceFile, classOpenBrace, inJs, call }; + } - let makeStatic = false; - let classDeclaration: ClassLikeDeclaration; + function getClassAndMakeStatic(token: Node, checker: TypeChecker): { readonly classDeclaration: ClassLikeDeclaration, readonly makeStatic: boolean } | undefined { + const { parent } = token; + if (!isPropertyAccessExpression(parent)) { + return undefined; + } - if (token.parent.expression.kind === SyntaxKind.ThisKeyword) { + if (parent.expression.kind === SyntaxKind.ThisKeyword) { const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); if (!isClassElement(containingClassMemberDeclaration)) { return undefined; } - - classDeclaration = containingClassMemberDeclaration.parent; - + const classDeclaration = containingClassMemberDeclaration.parent; // Property accesses on `this` in a static method are accesses of a static member. - makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static); + return isClassLike(classDeclaration) ? { classDeclaration, makeStatic: hasModifier(containingClassMemberDeclaration, ModifierFlags.Static) } : undefined; } else { - - const checker = context.program.getTypeChecker(); - const leftExpression = token.parent.expression; - const leftExpressionType = checker.getTypeAtLocation(leftExpression); - - if (leftExpressionType.flags & TypeFlags.Object) { - const symbol = leftExpressionType.symbol; - if (symbol.flags & SymbolFlags.Class) { - classDeclaration = symbol.declarations && symbol.declarations[0]; - if (leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol)) { - // The expression is a class symbol but the type is not the instance-side. - makeStatic = true; - } - } + const leftExpressionType = checker.getTypeAtLocation(parent.expression); + const { symbol } = leftExpressionType; + if (!(leftExpressionType.flags & TypeFlags.Object && symbol.flags & SymbolFlags.Class)) { + return undefined; } + const classDeclaration = cast(first(symbol.declarations), isClassLike); + // The expression is a class symbol but the type is not the instance-side. + return { classDeclaration, makeStatic: leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol) }; } + } - if (!classDeclaration || !isClassLike(classDeclaration)) { - return undefined; - } - - const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration); - const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile); - - return isInJavaScriptFile(classDeclarationSourceFile) ? - getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) : - getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic); - - function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { - let actions: CodeAction[]; + function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFix | undefined { + const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic, context.newLineCharacter)); + if (changes.length === 0) return undefined; + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); + return { description, changes, groupId }; + } - const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ false); - if (methodCodeAction) { - actions = [methodCodeAction]; + function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean, newLineCharacter: string): void { + if (makeStatic) { + if (classDeclaration.kind === SyntaxKind.ClassExpression) { + return; } - - if (makeStatic) { - if (classDeclaration.kind === SyntaxKind.ClassExpression) { - return actions; - } - - const className = classDeclaration.name.getText(); - - const staticInitialization = createStatement(createAssignment( - createPropertyAccess(createIdentifier(className), tokenName), - createIdentifier("undefined"))); - - const staticInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context); - staticInitializationChangeTracker.insertNodeAfter( - classDeclarationSourceFile, - classDeclaration, - staticInitialization, - { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); - const initializeStaticAction = { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [tokenName]), - changes: staticInitializationChangeTracker.getChanges() - }; - - (actions || (actions = [])).push(initializeStaticAction); - return actions; + const className = classDeclaration.name.getText(); + const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName); + changeTracker.insertNodeAfter(classDeclarationSourceFile, classDeclaration, staticInitialization, { prefix: newLineCharacter, suffix: newLineCharacter }); + } + else { + const classConstructor = getFirstConstructorWithBody(classDeclaration); + if (!classConstructor) { + return; } - else { - const classConstructor = getFirstConstructorWithBody(classDeclaration); - if (!classConstructor) { - return actions; - } - - const propertyInitialization = createStatement(createAssignment( - createPropertyAccess(createThis(), tokenName), - createIdentifier("undefined"))); + const propertyInitialization = initializePropertyToUndefined(createThis(), tokenName); + changeTracker.insertNodeBefore(classDeclarationSourceFile, classConstructor.body.getLastToken(), propertyInitialization, { suffix: newLineCharacter }); + } + } - const propertyInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context); - propertyInitializationChangeTracker.insertNodeBefore( - classDeclarationSourceFile, - classConstructor.body.getLastToken(), - propertyInitialization, - { suffix: context.newLineCharacter }); + function initializePropertyToUndefined(obj: Expression, propertyName: string) { + return createStatement(createAssignment(createPropertyAccess(obj, propertyName), createIdentifier("undefined"))); + } - const initializeAction = { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]), - changes: propertyInitializationChangeTracker.getChanges() - }; + function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, token: Identifier, classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeFix[] | undefined { + const typeNode = getTypeNode(context.program.getTypeChecker(), classDeclaration, token); + const addProp = createAddPropertyDeclarationAction(context, classDeclarationSourceFile, classOpenBrace, makeStatic, token.text, typeNode); + return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, classDeclarationSourceFile, classOpenBrace, token.text, typeNode)]; + } - (actions || (actions = [])).push(initializeAction); - return actions; - } + function getTypeNode(checker: TypeChecker, classDeclaration: ClassLikeDeclaration, token: Node) { + let typeNode: TypeNode; + if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { + const binaryExpression = token.parent.parent as BinaryExpression; + const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left; + const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression))); + typeNode = checker.typeToTypeNode(widenedType, classDeclaration); } + return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); + } - function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { - let actions: CodeAction[]; - - const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ true); - if (methodCodeAction) { - actions = [methodCodeAction]; - } + function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFix { + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); + const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classOpenBrace, tokenName, typeNode, makeStatic, context.newLineCharacter)); + return { description, changes, groupId }; + } - let typeNode: TypeNode; - if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { - const binaryExpression = token.parent.parent as BinaryExpression; - const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left; - const checker = context.program.getTypeChecker(); - const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression))); - typeNode = checker.typeToTypeNode(widenedType, classDeclaration); - } - typeNode = typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); - - const property = createProperty( - /*decorators*/undefined, - /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, - tokenName, - /*questionToken*/ undefined, - typeNode, - /*initializer*/ undefined); - const propertyChangeTracker = textChanges.ChangeTracker.fromContext(context); - propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter }); - - const diag = makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0; - actions = append(actions, { - description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), - changes: propertyChangeTracker.getChanges() - }); + function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, tokenName: string, typeNode: TypeNode, makeStatic: boolean, newLineCharacter: string): void { + const property = createProperty( + /*decorators*/ undefined, + /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, + tokenName, + /*questionToken*/ undefined, + typeNode, + /*initializer*/ undefined); + changeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: newLineCharacter }); + } - if (!makeStatic) { - // Index signatures cannot have the static modifier. - const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); - const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "x", - /*questionToken*/ undefined, - stringTypeNode, - /*initializer*/ undefined); - const indexSignature = createIndexSignature( - /*decorators*/ undefined, - /*modifiers*/ undefined, - [indexingParameter], - typeNode); - - const indexSignatureChangeTracker = textChanges.ChangeTracker.fromContext(context); - indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter }); - - actions.push({ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), - changes: indexSignatureChangeTracker.getChanges() - }); - } + function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, tokenName: string, typeNode: TypeNode): CodeFix { + // Index signatures cannot have the static modifier. + const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); + const indexingParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "x", + /*questionToken*/ undefined, + stringTypeNode, + /*initializer*/ undefined); + const indexSignature = createIndexSignature( + /*decorators*/ undefined, + /*modifiers*/ undefined, + [indexingParameter], + typeNode); + + const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter })); + // No groupId here because code-fix-all currently only works on adding individual named properties. + return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, groupId: undefined }; + } - return actions; - } + function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFix | undefined { + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); + const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classOpenBrace, token, callExpression, context.newLineCharacter, makeStatic, inJs)); + return { description, changes, groupId }; + } - function getActionForMethodDeclaration(includeTypeScriptSyntax: boolean): CodeAction | undefined { - if (token.parent.parent.kind === SyntaxKind.CallExpression) { - const callExpression = token.parent.parent; - const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, includeTypeScriptSyntax, makeStatic); - - const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromContext(context); - methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter }); - const diag = makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0; - return { - description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), - changes: methodDeclarationChangeTracker.getChanges() - }; - } - } + function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, token: Identifier, callExpression: CallExpression, newLineCharacter: string, makeStatic: boolean, inJs: boolean) { + const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic); + changeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: newLineCharacter }); } } diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 9796bf2fa22c4..21dd8792ac6a2 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,34 +1,41 @@ /* @internal */ namespace ts.codefix { + const groupId = "fixCannotFindModule"; + const errorCodes = [Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code]; registerCodeFix({ - errorCodes: [ - Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code, + errorCodes, + getCodeActions: context => [ + { groupId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) } ], - getCodeActions: context => { - const { sourceFile, span: { start } } = context; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - if (!isStringLiteral(token)) { - throw Debug.fail(); // These errors should only happen on the module name. + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (_, diag, commands) => { + const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start)); + if (pkg) { + commands.push(getCommand(diag.file.fileName, pkg)); } - - const action = tryGetCodeActionForInstallPackageTypes(context.host, sourceFile.fileName, token.text); - return action && [action]; - }, + }), }); - export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined { - const { packageName } = getPackageName(moduleName); + function getModuleName(sourceFile: SourceFile, pos: number): string { + return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isStringLiteral).text; + } - if (!host.isKnownTypesPackageName(packageName)) { - // If !registry, registry not available yet, can't do anything. - return undefined; - } + function getCommand(fileName: string, packageName: string): InstallPackageAction { + return { type: "install package", file: fileName, packageName }; + } - const typesPackageName = getTypesPackageName(packageName); - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [typesPackageName]), + function getTypesPackageNameToInstall(host: LanguageServiceHost, moduleName: string): string | undefined { + const { packageName } = getPackageName(moduleName); + // If !registry, registry not available yet, can't do anything. + return host.isKnownTypesPackageName(packageName) ? getTypesPackageName(packageName) : undefined; + } + + export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined { + const packageName = getTypesPackageNameToInstall(host, moduleName); + return packageName === undefined ? undefined : { + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [packageName]), changes: [], - commands: [{ type: "install package", file: fileName, packageName: typesPackageName }], + commands: [getCommand(fileName, packageName)], }; } } diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 768eaf752b780..bee7f20029554 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -1,52 +1,55 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [ + Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code, + Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code, + ]; + const groupId = "fixClassDoesntImplementInheritedAbstractMember"; registerCodeFix({ - errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code], - getCodeActions: getActionForClassLikeMissingAbstractMember + errorCodes, + getCodeActions(context) { + const { program, sourceFile, span } = context; + const changes = textChanges.ChangeTracker.with(context, t => + addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), context.newLineCharacter, t)); + return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), context.newLineCharacter, changes); + }), }); - registerCodeFix({ - errorCodes: [Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code], - getCodeActions: getActionForClassLikeMissingAbstractMember - }); - - function getActionForClassLikeMissingAbstractMember(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const start = context.span.start; + function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { // This is the identifier in the case of a class declaration // or the class keyword token in the case of a class expression. - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const checker = context.program.getTypeChecker(); - - if (isClassLike(token.parent)) { - const classDeclaration = token.parent as ClassLikeDeclaration; - - const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); - const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const classDeclaration = token.parent; + Debug.assert(isClassLike(classDeclaration)); + return classDeclaration as ClassLikeDeclaration; + } - // Note that this is ultimately derived from a map indexed by symbol names, - // so duplicates cannot occur. - const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType); - const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember); + function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, newLineCharacter: string, changeTracker: textChanges.ChangeTracker): void { + const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); + const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); - const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker); - const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context); - if (changes && changes.length > 0) { - return [{ - description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), - changes - }]; - } - } + // Note that this is ultimately derived from a map indexed by symbol names, + // so duplicates cannot occur. + const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember); - return undefined; + createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, addNewMemberToClass(changeTracker, sourceFile, classDeclaration, newLineCharacter)); + } + export function addNewMemberToClass(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, newLineCharacter: string): (newNode: Node) => void { + return newNode => { + Debug.assert(!!newNode); + changeTracker.insertNodeAfter(sourceFile, getOpenBraceOfClassLike(classDeclaration, sourceFile), newNode, { suffix: newLineCharacter }); + }; } function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean { - const decls = symbol.getDeclarations(); - Debug.assert(!!(decls && decls.length > 0)); - const flags = getModifierFlags(decls[0]); + // See `codeFixClassExtendAbstractProtectedProperty.ts` in https://github.com/Microsoft/TypeScript/pull/11547/files + // (now named `codeFixClassExtendAbstractPrivateProperty.ts`) + const flags = getModifierFlags(first(symbol.getDeclarations())); return !(flags & ModifierFlags.Private) && !!(flags & ModifierFlags.Abstract); } -} \ No newline at end of file +} diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 1a0cf17bbd663..83e15d34ae99d 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -1,64 +1,72 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code]; + const groupId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? registerCodeFix({ - errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code], - getCodeActions: getActionForClassLikeIncorrectImplementsInterface + errorCodes, + getCodeActions(context) { + const { newLineCharacter, program, sourceFile, span } = context; + const classDeclaration = getClass(sourceFile, span.start); + const checker = program.getTypeChecker(); + return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { + const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, newLineCharacter, t)); + if (changes.length === 0) return undefined; + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); + return { description, changes, groupId }; + }); + }, + groupIds: [groupId], + fixAllInGroup(context) { + const seenClassDeclarations: true[] = []; + return codeFixAll(context, errorCodes, (changes, diag) => { + const classDeclaration = getClass(diag.file!, diag.start!); + if (addToSeenIds(seenClassDeclarations, getNodeId(classDeclaration))) { + for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) { + addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, context.newLineCharacter, changes); + } + } + }); + }, }); - function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const checker = context.program.getTypeChecker(); - - const classDeclaration = getContainingClass(token); - if (!classDeclaration) { - return undefined; - } + function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { + const classDeclaration = getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false)); + Debug.assert(!!classDeclaration); + return classDeclaration!; + } - const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); - const classType = checker.getTypeAtLocation(classDeclaration) as InterfaceType; - const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDeclaration); + function addMissingDeclarations( + checker: TypeChecker, + implementedTypeNode: ExpressionWithTypeArguments, + sourceFile: SourceFile, + classDeclaration: ClassLikeDeclaration, + newLineCharacter: string, + changeTracker: textChanges.ChangeTracker + ): void { + // Note that this is ultimately derived from a map indexed by symbol names, + // so duplicates cannot occur. + const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; + const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); + const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); - const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number); - const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String); + const classType = checker.getTypeAtLocation(classDeclaration); - const result: CodeAction[] = []; - for (const implementedTypeNode of implementedTypeNodes) { - // Note that this is ultimately derived from a map indexed by symbol names, - // so duplicates cannot occur. - const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; - const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); - const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); + const insert = addNewMemberToClass(changeTracker, sourceFile, classDeclaration, newLineCharacter); - let newNodes: Node[] = []; - createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes); - createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes); - newNodes = newNodes.concat(createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker)); - const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); - if (newNodes.length > 0) { - pushAction(result, newNodes, message); - } + if (!checker.getIndexTypeOfType(classType, IndexKind.Number)) { + createMissingIndexSignatureDeclaration(implementedType, IndexKind.Number); + } + if (!checker.getIndexTypeOfType(classType, IndexKind.String)) { + createMissingIndexSignatureDeclaration(implementedType, IndexKind.String); } - return result; - - function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void { - if (hasIndexSigOfKind) { - return; - } + createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, insert); + function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); - - if (!indexInfoOfKind) { - return; + if (indexInfoOfKind) { + insert(checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration)); } - const newIndexSignatureDeclaration = checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration); - newNodes.push(newIndexSignatureDeclaration); - } - - function pushAction(result: CodeAction[], newNodes: Node[], description: string): void { - result.push({ description, changes: newNodesToChanges(newNodes, openBrace, context) }); } } -} \ No newline at end of file +} diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index e5c4266684d22..901b9b827402c 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -1,49 +1,52 @@ /* @internal */ namespace ts.codefix { + const groupId = "classSuperMustPrecedeThisAccess"; + const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code]; registerCodeFix({ - errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - if (token.kind !== SyntaxKind.ThisKeyword) { - return undefined; - } - - const constructor = getContainingFunction(token); - const superCall = findSuperCall((constructor).body); - if (!superCall) { - return undefined; - } - - // figure out if the `this` access is actually inside the supercall - // i.e. super(this.a), since in that case we won't suggest a fix - if (superCall.expression && superCall.expression.kind === SyntaxKind.CallExpression) { - const expressionArguments = (superCall.expression).arguments; - for (const arg of expressionArguments) { - if ((arg).expression === token) { - return undefined; - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const nodes = getNodes(sourceFile, context.span.start); + if (!nodes) return undefined; + const { constructor, superCall } = nodes; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall, context.newLineCharacter)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup(context) { + const { newLineCharacter, sourceFile } = context; + const seenClasses: true[] = []; // Ensure we only do this once per class. + return codeFixAll(context, errorCodes, (changes, diag) => { + const nodes = getNodes(diag.file!, diag.start!); + if (!nodes) return; + const { constructor, superCall } = nodes; + if (addToSeenIds(seenClasses, getNodeId(constructor.parent))) { + doChange(changes, sourceFile, constructor, superCall, newLineCharacter); } - } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { suffix: context.newLineCharacter }); - changeTracker.deleteNode(sourceFile, superCall); + }); + }, + }); - return [{ - description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), - changes: changeTracker.getChanges() - }]; + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, constructor: ConstructorDeclaration, superCall: ExpressionStatement, newLineCharacter: string): void { + changes.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { suffix: newLineCharacter }); + changes.deleteNode(sourceFile, superCall); + } - function findSuperCall(n: Node): ExpressionStatement { - if (n.kind === SyntaxKind.ExpressionStatement && isSuperCall((n).expression)) { - return n; - } - if (isFunctionLike(n)) { - return undefined; - } - return forEachChild(n, findSuperCall); - } - } - }); + function getNodes(sourceFile: SourceFile, pos: number): { readonly constructor: ConstructorDeclaration, readonly superCall: ExpressionStatement } { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + Debug.assert(token.kind === SyntaxKind.ThisKeyword); + const constructor = getContainingFunction(token) as ConstructorDeclaration; + const superCall = findSuperCall(constructor.body); + // figure out if the `this` access is actually inside the supercall + // i.e. super(this.a), since in that case we won't suggest a fix + return superCall && !superCall.expression.arguments.some(arg => isPropertyAccessExpression(arg) && arg.expression === token) ? { constructor, superCall } : undefined; + } + + function findSuperCall(n: Node): ExpressionStatement & { expression: CallExpression } | undefined { + return isExpressionStatement(n) && isSuperCall(n.expression) + ? n as ExpressionStatement & { expression: CallExpression } + : isFunctionLike(n) + ? undefined + : forEachChild(n, findSuperCall); + } } diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 24f44a877b34e..3d36071b4de73 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -1,23 +1,28 @@ /* @internal */ namespace ts.codefix { + const groupId = "constructorForDerivedNeedSuperCall"; + const errorCodes = [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code]; registerCodeFix({ - errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - - if (token.kind !== SyntaxKind.ConstructorKeyword) { - return undefined; - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const ctr = getNode(sourceFile, context.span.start); + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr, context.newLineCharacter)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => + doChange(changes, context.sourceFile, getNode(diag.file, diag.start!), context.newLineCharacter)), + }); - const changeTracker = textChanges.ChangeTracker.fromContext(context); - const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { suffix: context.newLineCharacter }); + function getNode(sourceFile: SourceFile, pos: number): ConstructorDeclaration { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + Debug.assert(token.kind === SyntaxKind.ConstructorKeyword); + return token.parent as ConstructorDeclaration; + } - return [{ - description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), - changes: changeTracker.getChanges() - }]; - } - }); -} \ No newline at end of file + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, ctr: ConstructorDeclaration, newLineCharacter: string) { + const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); + changes.insertNodeAfter(sourceFile, getOpenBrace(ctr, sourceFile), superCall, { suffix: newLineCharacter }); + } +} diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index 57bf2dd279511..d366a95439f68 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -1,43 +1,37 @@ /* @internal */ namespace ts.codefix { + const groupId = "extendsInterfaceBecomesImplements"; + const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const classDeclNode = getContainingClass(token); - if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) { - return undefined; - } - - const heritageClauses = classDeclNode.heritageClauses; - if (!(heritageClauses && heritageClauses.length > 0)) { - return undefined; - } - - const extendsToken = heritageClauses[0].getFirstToken(); - if (!(extendsToken && extendsToken.kind === SyntaxKind.ExtendsKeyword)) { - return undefined; - } - - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword)); - - // We replace existing keywords with commas. - for (let i = 1; i < heritageClauses.length; i++) { - const keywordToken = heritageClauses[i].getFirstToken(); - if (keywordToken) { - changeTracker.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken)); - } - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const nodes = getNodes(sourceFile, context.span.start); + if (!nodes) return undefined; + const { extendsToken, heritageClauses } = nodes; + const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + const nodes = getNodes(diag.file, diag.start!); + if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses); + }), + }); - const result = [{ - description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), - changes: changeTracker.getChanges() - }]; + function getNodes(sourceFile: SourceFile, pos: number) { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const heritageClauses = getContainingClass(token)!.heritageClauses; + const extendsToken = heritageClauses[0].getFirstToken(); + return extendsToken.kind === SyntaxKind.ExtendsKeyword ? { extendsToken, heritageClauses } : undefined; + } - return result; + function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray): void { + changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword)); + // We replace existing keywords with commas. + for (let i = 1; i < heritageClauses.length; i++) { + const keywordToken = heritageClauses[i].getFirstToken()!; + changes.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken)); } - }); + } } diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 5ac4f035f736f..fb6cf36e89fa1 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -1,20 +1,26 @@ /* @internal */ namespace ts.codefix { + const groupId = "forgottenThisPropertyAccess"; + const errorCodes = [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - if (token.kind !== SyntaxKind.Identifier) { - return undefined; - } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token)); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), - changes: changeTracker.getChanges() - }]; - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const token = getNode(sourceFile, context.span.start); + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + doChange(changes, context.sourceFile, getNode(diag.file, diag.start!)); + }), }); + + function getNode(sourceFile: SourceFile, pos: number): Identifier { + return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isIdentifier); + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Identifier): void { + changes.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token)); + } } \ No newline at end of file diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts index 8d5cd562497fb..c84b5aa52420d 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -1,61 +1,91 @@ /* @internal */ namespace ts.codefix { + const groupIdPlain = "fixJSDocTypes_plain"; + const groupIdNullable = "fixJSDocTypes_nullable"; + const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code]; registerCodeFix({ - errorCodes: [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code], - getCodeActions: getActionsForJSDocTypes + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const checker = context.program.getTypeChecker(); + const info = getInfo(sourceFile, context.span.start, checker); + if (!info) return undefined; + const { typeNode, type } = info; + const original = typeNode.getText(sourceFile); + const actions = [fix(type, groupIdPlain)]; + if (typeNode.kind === SyntaxKind.JSDocNullableType) { + // for nullable types, suggest the flow-compatible `T | null | undefined` + // in addition to the jsdoc/closure-compatible `T | null` + actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), groupIdNullable)); + } + return actions; + + function fix(type: Type, groupId: string): CodeFix { + const newText = typeString(type, checker); + return { + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]), + changes: [createFileTextChanges(sourceFile.fileName, [createChange(typeNode, sourceFile, newText)])], + groupId, + }; + } + }, + groupIds: [groupIdPlain, groupIdNullable], + fixAllInGroup(context) { + const { groupId, program, sourceFile } = context; + const checker = program.getTypeChecker(); + return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + const info = getInfo(err.file, err.start!, checker); + if (!info) return; + const { typeNode, type } = info; + const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && groupId === groupIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; + changes.push(createChange(typeNode, sourceFile, typeString(fixedType, checker))); + }); + } }); - function getActionsForJSDocTypes(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); + function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { readonly typeNode: TypeNode, type: Type } { + const decl = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isTypeContainer); + const typeNode = decl && decl.type; + return typeNode && { typeNode, type: checker.getTypeFromTypeNode(typeNode) }; + } - // NOTE: Some locations are not handled yet: - // MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments - const decl = ts.findAncestor(node, - n => - n.kind === SyntaxKind.AsExpression || - n.kind === SyntaxKind.CallSignature || - n.kind === SyntaxKind.ConstructSignature || - n.kind === SyntaxKind.FunctionDeclaration || - n.kind === SyntaxKind.GetAccessor || - n.kind === SyntaxKind.IndexSignature || - n.kind === SyntaxKind.MappedType || - n.kind === SyntaxKind.MethodDeclaration || - n.kind === SyntaxKind.MethodSignature || - n.kind === SyntaxKind.Parameter || - n.kind === SyntaxKind.PropertyDeclaration || - n.kind === SyntaxKind.PropertySignature || - n.kind === SyntaxKind.SetAccessor || - n.kind === SyntaxKind.TypeAliasDeclaration || - n.kind === SyntaxKind.TypeAssertionExpression || - n.kind === SyntaxKind.VariableDeclaration); - if (!decl) return; - const checker = context.program.getTypeChecker(); + function createChange(declaration: TypeNode, sourceFile: SourceFile, newText: string): TextChange { + return { span: createTextSpanFromBounds(declaration.getStart(sourceFile), declaration.getEnd()), newText }; + } - const jsdocType = (decl as VariableDeclaration).type; - if (!jsdocType) return; - const original = getTextOfNode(jsdocType); - const type = checker.getTypeFromTypeNode(jsdocType); - const actions = [createAction(jsdocType, sourceFile.fileName, original, checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation))]; - if (jsdocType.kind === SyntaxKind.JSDocNullableType) { - // for nullable types, suggest the flow-compatible `T | null | undefined` - // in addition to the jsdoc/closure-compatible `T | null` - const replacementWithUndefined = checker.typeToString(checker.getNullableType(type, TypeFlags.Undefined), /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation); - actions.push(createAction(jsdocType, sourceFile.fileName, original, replacementWithUndefined)); - } - return actions; + function typeString(type: Type, checker: TypeChecker): string { + return checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation); } - function createAction(declaration: TypeNode, fileName: string, original: string, replacement: string): CodeAction { - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, replacement]), - changes: [{ - fileName, - textChanges: [{ - span: { start: declaration.getStart(), length: declaration.getWidth() }, - newText: replacement - }] - }], - }; + // TODO: GH#19856 Node & { type: TypeNode } + type TypeContainer = + | AsExpression | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration + | GetAccessorDeclaration | IndexSignatureDeclaration | MappedTypeNode | MethodDeclaration + | MethodSignature | ParameterDeclaration | PropertyDeclaration | PropertySignature | SetAccessorDeclaration + | TypeAliasDeclaration | TypeAssertion | VariableDeclaration; + function isTypeContainer(node: Node): node is TypeContainer { + // NOTE: Some locations are not handled yet: + // MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.IndexSignature: + case SyntaxKind.MappedType: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.SetAccessor: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.VariableDeclaration: + return true; + default: + return false; + } } } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 2546a92a32fce..2347cd8ff9cff 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -1,19 +1,34 @@ /* @internal */ namespace ts.codefix { + const groupId = "fixSpelling"; + const errorCodes = [ + Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, + Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, + ]; registerCodeFix({ - errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, - Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], - getCodeActions: getActionsForCorrectSpelling + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker()); + if (!info) return undefined; + const { node, suggestion } = info; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); + return [{ description, changes, groupId }]; + }, + groupIds: [groupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); + if (info) doChange(changes, context.sourceFile, info.node, info.suggestion); + }), }); - function getActionsForCorrectSpelling(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - + function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { node: Node, suggestion: string } | undefined { // This is the identifier of the misspelled word. eg: // this.speling = 1; // ^^^^^^^ - const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); // TODO: GH#15852 - const checker = context.program.getTypeChecker(); + const node = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); // TODO: GH#15852 + let suggestion: string; if (isPropertyAccessExpression(node.parent) && node.parent.name === node) { Debug.assert(node.kind === SyntaxKind.Identifier); @@ -26,18 +41,12 @@ namespace ts.codefix { Debug.assert(name !== undefined, "name should be defined"); suggestion = checker.getSuggestionForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning)); } - if (suggestion) { - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: node.getStart(), length: node.getWidth() }, - newText: suggestion - }], - }], - }]; - } + + return suggestion === undefined ? undefined : { node, suggestion }; + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestion: string) { + changes.replaceNode(sourceFile, node, createIdentifier(suggestion)); } function convertSemanticMeaningToSymbolFlags(meaning: SemanticMeaning): SymbolFlags { diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 090aefbb8cafc..f468b82c38cea 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -1,205 +1,234 @@ /* @internal */ namespace ts.codefix { + const prefixGroupId = "unusedIdentifier_prefix"; + const deleteGroupId = "unusedIdentifier_delete"; + const errorCodes = [ + Diagnostics._0_is_declared_but_its_value_is_never_read.code, + Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code, + ]; registerCodeFix({ - errorCodes: [ - Diagnostics._0_is_declared_but_its_value_is_never_read.code, - Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code - ], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const start = context.span.start; - - let token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - - // this handles var ["computed"] = 12; - if (token.kind === SyntaxKind.OpenBracketToken) { - token = getTokenAtPosition(sourceFile, start + 1, /*includeJsDocComment*/ false); + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const token = getToken(sourceFile, context.span.start); + const result: CodeFix[] = []; + + const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); + if (deletion.length) { + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); + result.push({ description, changes: deletion, groupId: deleteGroupId }); } - switch (token.kind) { - case ts.SyntaxKind.Identifier: - return deleteIdentifierOrPrefixWithUnderscore(token); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.NamespaceImport: - return [deleteNode(token.parent)]; + const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, sourceFile, token)); + if (prefix.length) { + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); + result.push({ description, changes: prefix, groupId: prefixGroupId }); + } + return result; + }, + groupIds: [prefixGroupId, deleteGroupId], + fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + const { sourceFile } = context; + const token = getToken(diag.file!, diag.start!); + switch (context.groupId) { + case prefixGroupId: + if (isIdentifier(token) && canPrefix(token)) { + tryPrefixDeclaration(changes, sourceFile, token); + } + break; + case deleteGroupId: + tryDeleteDeclaration(changes, sourceFile, token); + break; default: - return deleteDefault(); + Debug.fail(JSON.stringify(context.groupId)); } + }), + }); - function deleteDefault(): CodeAction[] | undefined { - if (isDeclarationName(token)) { - return [deleteNode(token.parent)]; + function getToken(sourceFile: SourceFile, pos: number): Node { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + // this handles var ["computed"] = 12; + return token.kind === SyntaxKind.OpenBracketToken ? getTokenAtPosition(sourceFile, pos + 1, /*includeJsDocComment*/ false) : token; + } + + function tryPrefixDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void { + if (isIdentifier(token) && canPrefix(token)) { + changes.replaceNode(sourceFile, token, createIdentifier(`_${token.text}`)); + } + } + + function canPrefix(token: Identifier): boolean { + switch (token.parent.kind) { + case SyntaxKind.Parameter: + return true; + case SyntaxKind.VariableDeclaration: { + const varDecl = token.parent as VariableDeclaration; + switch (varDecl.parent.parent.kind) { + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + return true; } - else if (isLiteralComputedPropertyDeclarationName(token)) { - return [deleteNode(token.parent.parent)]; + } + } + return false; + } + + function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void { + switch (token.kind) { + case SyntaxKind.Identifier: + tryDeleteIdentifier(changes, sourceFile, token); + break; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.NamespaceImport: + changes.deleteNode(sourceFile, token.parent); + break; + default: + tryDeleteDefault(changes, sourceFile, token); + } + } + + function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void { + if (isDeclarationName(token)) { + changes.deleteNode(sourceFile, token.parent); + } + else if (isLiteralComputedPropertyDeclarationName(token)) { + changes.deleteNode(sourceFile, token.parent.parent); + } + } + + function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier): void { + const parent = identifier.parent; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + tryDeleteVariableDeclaration(changes, sourceFile, parent); + break; + + case SyntaxKind.TypeParameter: + const typeParameters = (parent.parent).typeParameters; + if (typeParameters.length === 1) { + const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); + const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); + Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); + Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); + + changes.deleteNodeRange(sourceFile, previousToken, nextToken); } else { - return undefined; + changes.deleteNodeInList(sourceFile, parent); } - } + break; - function prefixIdentifierWithUnderscore(identifier: Identifier): CodeAction { - const startPosition = identifier.getStart(sourceFile, /*includeJsDocComment*/ false); - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), { 0: token.getText() }), - changes: [{ - fileName: sourceFile.path, - textChanges: [{ - span: { start: startPosition, length: 0 }, - newText: "_" - }] - }] - }; - } - - function deleteIdentifierOrPrefixWithUnderscore(identifier: Identifier): CodeAction[] | undefined { - const parent = identifier.parent; - switch (parent.kind) { - case ts.SyntaxKind.VariableDeclaration: - return deleteVariableDeclarationOrPrefixWithUnderscore(identifier, parent); - - case SyntaxKind.TypeParameter: - const typeParameters = (parent.parent).typeParameters; - if (typeParameters.length === 1) { - const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); - const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); - Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); - Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); - - return [deleteNodeRange(previousToken, nextToken)]; - } - else { - return [deleteNodeInList(parent)]; - } - - case ts.SyntaxKind.Parameter: - const functionDeclaration = parent.parent; - return [functionDeclaration.parameters.length === 1 ? deleteNode(parent) : deleteNodeInList(parent), - prefixIdentifierWithUnderscore(identifier)]; - - // handle case where 'import a = A;' - case SyntaxKind.ImportEqualsDeclaration: - const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration); - return [deleteNode(importEquals)]; - - case SyntaxKind.ImportSpecifier: - const namedImports = parent.parent; - if (namedImports.elements.length === 1) { - return deleteNamedImportBinding(namedImports); - } - else { - // delete import specifier - return [deleteNodeInList(parent)]; - } - - case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' - const importClause = parent; - if (!importClause.namedBindings) { // |import d from './file'| - const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); - return [deleteNode(importDecl)]; - } - else { - // import |d,| * as ns from './file' - const start = importClause.name.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false); - if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { - // shift first non-whitespace position after comma to the start position of the node - return [deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) })]; - } - else { - return [deleteNode(importClause.name)]; - } - } - - case SyntaxKind.NamespaceImport: - return deleteNamedImportBinding(parent); - - default: - return deleteDefault(); + case SyntaxKind.Parameter: + const functionDeclaration = parent.parent; + if (functionDeclaration.parameters.length === 1) { + changes.deleteNode(sourceFile, parent); } - } - - function deleteNamedImportBinding(namedBindings: NamedImportBindings): CodeAction[] | undefined { - if ((namedBindings.parent).name) { - // Delete named imports while preserving the default import - // import d|, * as ns| from './file' - // import d|, { a }| from './file' - const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false); - if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - return [deleteRange({ pos: previousToken.getStart(), end: namedBindings.end })]; - } - return undefined; + else { + changes.deleteNodeInList(sourceFile, parent); + } + break; + + // handle case where 'import a = A;' + case SyntaxKind.ImportEqualsDeclaration: + const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration); + changes.deleteNode(sourceFile, importEquals); + break; + + case SyntaxKind.ImportSpecifier: + const namedImports = parent.parent; + if (namedImports.elements.length === 1) { + tryDeleteNamedImportBinding(changes, sourceFile, namedImports); } else { - // Delete the entire import declaration - // |import * as ns from './file'| - // |import { a } from './file'| - const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration); - return [deleteNode(importDecl)]; + // delete import specifier + changes.deleteNodeInList(sourceFile, parent); } - } - - // token.parent is a variableDeclaration - function deleteVariableDeclarationOrPrefixWithUnderscore(identifier: Identifier, varDecl: ts.VariableDeclaration): CodeAction[] | undefined { - switch (varDecl.parent.parent.kind) { - case SyntaxKind.ForStatement: - const forStatement = varDecl.parent.parent; - const forInitializer = forStatement.initializer; - return [forInitializer.declarations.length === 1 ? deleteNode(forInitializer) : deleteNodeInList(varDecl)]; + break; - case SyntaxKind.ForOfStatement: - const forOfStatement = varDecl.parent.parent; - Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList); - const forOfInitializer = forOfStatement.initializer; - return [ - replaceNode(forOfInitializer.declarations[0], createObjectLiteral()), - prefixIdentifierWithUnderscore(identifier) - ]; - - case SyntaxKind.ForInStatement: - // There is no valid fix in the case of: - // for .. in - return [prefixIdentifierWithUnderscore(identifier)]; - - default: - const variableStatement = varDecl.parent.parent; - if (variableStatement.declarationList.declarations.length === 1) { - return [deleteNode(variableStatement)]; - } - else { - return [deleteNodeInList(varDecl)]; - } + case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' + const importClause = parent; + if (!importClause.namedBindings) { // |import d from './file'| + changes.deleteNode(sourceFile, getAncestor(importClause, SyntaxKind.ImportDeclaration)!); } - } - - function deleteNode(n: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNode(sourceFile, n)); - } - - function deleteRange(range: TextRange) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteRange(sourceFile, range)); - } + else { + // import |d,| * as ns from './file' + const start = importClause.name.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); + changes.deleteRange(sourceFile, { pos: start, end }); + } + else { + changes.deleteNode(sourceFile, importClause.name); + } + } + break; - function deleteNodeInList(n: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeInList(sourceFile, n)); - } + case SyntaxKind.NamespaceImport: + tryDeleteNamedImportBinding(changes, sourceFile, parent); + break; - function deleteNodeRange(start: Node, end: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeRange(sourceFile, start, end)); + default: + tryDeleteDefault(changes, sourceFile, identifier); + break; + } + } + + function tryDeleteNamedImportBinding(changes: textChanges.ChangeTracker, sourceFile: SourceFile, namedBindings: NamedImportBindings): void { + if ((namedBindings.parent).name) { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false); + if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { + changes.deleteRange(sourceFile, { pos: previousToken.getStart(), end: namedBindings.end }); } - - function replaceNode(n: Node, newNode: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).replaceNode(sourceFile, n, newNode)); + } + else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration); + changes.deleteNode(sourceFile, importDecl); + } + } + + // token.parent is a variableDeclaration + function tryDeleteVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, varDecl: VariableDeclaration): void { + switch (varDecl.parent.parent.kind) { + case SyntaxKind.ForStatement: { + const forStatement = varDecl.parent.parent; + const forInitializer = forStatement.initializer; + if (forInitializer.declarations.length === 1) { + changes.deleteNode(sourceFile, forInitializer); + } + else { + changes.deleteNodeInList(sourceFile, varDecl); + } + break; } - function makeChange(changeTracker: textChanges.ChangeTracker): CodeAction { - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }), - changes: changeTracker.getChanges() - }; - } + case SyntaxKind.ForOfStatement: + const forOfStatement = varDecl.parent.parent; + Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList); + const forOfInitializer = forOfStatement.initializer; + changes.replaceNode(sourceFile, forOfInitializer.declarations[0], createObjectLiteral()); + break; + + case SyntaxKind.ForInStatement: + case SyntaxKind.TryStatement: + break; + + default: + const variableStatement = varDecl.parent.parent; + if (variableStatement.declarationList.declarations.length === 1) { + changes.deleteNode(sourceFile, variableStatement); + } + else { + changes.deleteNodeInList(sourceFile, varDecl); + } } - }); + } } \ No newline at end of file diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 206a3864ac052..22b6f325a717c 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -1,61 +1,24 @@ /* @internal */ namespace ts.codefix { - - export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { - const sourceFile = context.sourceFile; - - const changeTracker = textChanges.ChangeTracker.fromContext(context); - - for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); - } - - const changes = changeTracker.getChanges(); - if (!some(changes)) { - return changes; - } - - Debug.assert(changes.length === 1); - const consolidatedChanges: FileTextChanges[] = [{ - fileName: changes[0].fileName, - textChanges: [{ - span: changes[0].textChanges[0].span, - newText: changes[0].textChanges.reduce((prev, cur) => prev + cur.newText, "") - }] - - }]; - return consolidatedChanges; - } - /** * Finds members of the resolved type that are missing in the class pointed to by class decl * and generates source code for the missing members. * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for. * @returns Empty string iff there are no member insertions. */ - export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker): Node[] { + export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray, checker: TypeChecker, out: (node: Node) => void): void { const classMembers = classDeclaration.symbol.members; - const missingMembers = possiblyMissingSymbols.filter(symbol => !classMembers.has(symbol.escapedName)); - - let newNodes: Node[] = []; - for (const symbol of missingMembers) { - const newNode = createNewNodeForMemberSymbol(symbol, classDeclaration, checker); - if (newNode) { - if (Array.isArray(newNode)) { - newNodes = newNodes.concat(newNode); - } - else { - newNodes.push(newNode); - } + for (const symbol of possiblyMissingSymbols) { + if (!classMembers.has(symbol.escapedName)) { + addNewNodeForMemberSymbol(symbol, classDeclaration, checker, out); } } - return newNodes; } /** * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`. */ - function createNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { + function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, out: (node: Node) => void): void { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; @@ -75,14 +38,14 @@ namespace ts.codefix { case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: const typeNode = checker.typeToTypeNode(type, enclosingDeclaration); - const property = createProperty( + out(createProperty( /*decorators*/undefined, modifiers, name, optional ? createToken(SyntaxKind.QuestionToken) : undefined, typeNode, - /*initializer*/ undefined); - return property; + /*initializer*/ undefined)); + break; case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: // The signature for the implementation appears as an entry in `signatures` iff @@ -94,81 +57,61 @@ namespace ts.codefix { // correspondence of declarations and signatures. const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); if (!some(signatures)) { - return undefined; + break; } if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - return signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + break; } - const signatureDeclarations: MethodDeclaration[] = []; for (const signature of signatures) { - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + signatureToMethodDeclaration(signature, enclosingDeclaration); } if (declarations.length > signatures.length) { const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); } else { Debug.assert(declarations.length === signatures.length); - const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, optional, modifiers); - signatureDeclarations.push(methodImplementingSignatures); + out(createMethodImplementingSignatures(signatures, name, optional, modifiers)); } - return signatureDeclarations; - default: - return undefined; + break; } function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); - if (signatureDeclaration) { - signatureDeclaration.decorators = undefined; - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclaration.body = body; + if (!signatureDeclaration) { + return; } - return signatureDeclaration; - } - } - - export function createMethodFromCallExpression(callExpression: CallExpression, methodName: string, includeTypeScriptSyntax: boolean, makeStatic: boolean): MethodDeclaration { - const parameters = createDummyParameters(callExpression.arguments.length, /*names*/ undefined, /*minArgumentCount*/ undefined, includeTypeScriptSyntax); - let typeParameters: TypeParameterDeclaration[]; - if (includeTypeScriptSyntax) { - const typeArgCount = length(callExpression.typeArguments); - for (let i = 0; i < typeArgCount; i++) { - const name = typeArgCount < 8 ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`; - const typeParameter = createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined); - (typeParameters ? typeParameters : typeParameters = []).push(typeParameter); - } + signatureDeclaration.decorators = undefined; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = body; + out(signatureDeclaration); } + } - const newMethod = createMethod( + export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration { + return createMethod( /*decorators*/ undefined, /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, /*asteriskToken*/ undefined, methodName, /*questionToken*/ undefined, - typeParameters, - parameters, - /*type*/ includeTypeScriptSyntax ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined, - createStubbedMethodBody() - ); - return newMethod; + /*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) => + createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)), + /*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs), + /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), + createStubbedMethodBody()); } - function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, addAnyType: boolean) { + function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] { const parameters: ParameterDeclaration[] = []; for (let i = 0; i < argCount; i++) { const newParameter = createParameter( @@ -177,11 +120,10 @@ namespace ts.codefix { /*dotDotDotToken*/ undefined, /*name*/ names && names[i] || `arg${i}`, /*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, - /*type*/ addAnyType ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined, + /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); parameters.push(newParameter); } - return parameters; } @@ -205,7 +147,7 @@ namespace ts.codefix { const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name); - const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*addAnyType*/ true); + const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*inJs*/ false); if (someSigHasRestParameter) { const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); @@ -229,7 +171,7 @@ namespace ts.codefix { /*returnType*/ undefined); } - export function createStubbedMethod( + function createStubbedMethod( modifiers: ReadonlyArray, name: PropertyName, optional: boolean, @@ -258,7 +200,7 @@ namespace ts.codefix { /*multiline*/ true); } - function createVisibilityModifier(flags: ModifierFlags) { + function createVisibilityModifier(flags: ModifierFlags): Modifier | undefined { if (flags & ModifierFlags.Public) { return createToken(SyntaxKind.PublicKeyword); } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 41515265862d5..c204d0cd9178c 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -9,14 +9,17 @@ namespace ts.codefix { Diagnostics.Cannot_find_namespace_0.code, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code ], - getCodeActions: getImportCodeActions + getCodeActions: getImportCodeActions, + // TODO: GH#20315 + groupIds: [], + fixAllInGroup: notImplemented, }); type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport"; // Map from module Id to an array of import declarations in that module. type ImportDeclarationMap = AnyImportSyntax[][]; - interface ImportCodeAction extends CodeAction { + interface ImportCodeAction extends CodeFix { kind: ImportCodeActionKind; moduleSpecifier?: string; } @@ -154,6 +157,8 @@ namespace ts.codefix { return { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), changes, + // TODO: GH#20315 + groupId: undefined, kind, moduleSpecifier }; diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 3f03180360d2e..4b53804f458d6 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1,270 +1,257 @@ /* @internal */ namespace ts.codefix { - registerCodeFix({ - errorCodes: [ - // Variable declarations - Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, + const groupId = "inferFromUsage"; + const errorCodes = [ + // Variable declarations + Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, - // Variable uses - Diagnostics.Variable_0_implicitly_has_an_1_type.code, + // Variable uses + Diagnostics.Variable_0_implicitly_has_an_1_type.code, - // Parameter declarations - Diagnostics.Parameter_0_implicitly_has_an_1_type.code, - Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code, + // Parameter declarations + Diagnostics.Parameter_0_implicitly_has_an_1_type.code, + Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code, - // Get Accessor declarations - Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code, - Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code, + // Get Accessor declarations + Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code, + Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code, - // Set Accessor declarations - Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code, + // Set Accessor declarations + Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code, - // Property declarations - Diagnostics.Member_0_implicitly_has_an_1_type.code, - ], - getCodeActions: getActionsForAddExplicitTypeAnnotation + // Property declarations + Diagnostics.Member_0_implicitly_has_an_1_type.code, + ]; + registerCodeFix({ + errorCodes, + getCodeActions({ sourceFile, program, span: { start }, errorCode, cancellationToken }) { + if (isSourceFileJavaScript(sourceFile)) { + return undefined; // TODO: GH#20113 + } + + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); + const fix = getFix(sourceFile, token, errorCode, program, cancellationToken); + if (!fix) return undefined; + + const { declaration, textChanges } = fix; + const name = getNameOfDeclaration(declaration); + const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name.getText()]); + return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], groupId }]; + }, + groupIds: [groupId], + fixAllInGroup(context) { + const { sourceFile, program, cancellationToken } = context; + const seenFunctions: true[] = []; + return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + const fix = getFix(sourceFile, getTokenAtPosition(err.file!, err.start!, /*includeJsDocComment*/ false), err.code, program, cancellationToken, seenFunctions); + if (fix) changes.push(...fix.textChanges); + }); + }, }); - function getActionsForAddExplicitTypeAnnotation({ sourceFile, program, span: { start }, errorCode, cancellationToken }: CodeFixContext): CodeAction[] | undefined { - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - let writer: StringSymbolWriter; - - if (isInJavaScriptFile(token)) { - return undefined; + function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage { + switch (errorCode) { + case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: + return isSetAccessor(getContainingFunction(token)) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; + case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: + return Diagnostics.Infer_parameter_types_from_usage; + default: + return Diagnostics.Infer_type_of_0_from_usage; } + } - switch (token.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.DotDotDotToken: - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - // Allowed - break; - default: - return undefined; + interface Fix { + readonly declaration: Declaration; + readonly textChanges: TextChange[]; + } + + function getFix(sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: true[]): Fix | undefined { + if (!isAllowedTokenKind(token.kind)) { + return undefined; } const containingFunction = getContainingFunction(token); - const checker = program.getTypeChecker(); - switch (errorCode) { // Variable and Property declarations case Diagnostics.Member_0_implicitly_has_an_1_type.code: case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: - return getCodeActionForVariableDeclaration(token.parent); - case Diagnostics.Variable_0_implicitly_has_an_1_type.code: - return getCodeActionForVariableUsage(token); + return getCodeActionForVariableDeclaration(token.parent, sourceFile, program, cancellationToken); + + case Diagnostics.Variable_0_implicitly_has_an_1_type.code: { + const symbol = program.getTypeChecker().getSymbolAtLocation(token); + return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(symbol.valueDeclaration, sourceFile, program, cancellationToken); + } // Parameter declarations case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: if (isSetAccessor(containingFunction)) { - return getCodeActionForSetAccessor(containingFunction); + return getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken); } - // falls through + // falls through case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: - return getCodeActionForParameters(token.parent); + return !seenFunctions || addToSeenIds(seenFunctions, getNodeId(containingFunction)) + ? getCodeActionForParameters(token.parent, containingFunction, sourceFile, program, cancellationToken) + : undefined; // Get Accessor declarations case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code: case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code: - return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction) : undefined; + return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined; // Set Accessor declarations case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: - return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction) : undefined; - } - - return undefined; - - function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature) { - if (!isIdentifier(declaration.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(declaration.name); - const typeString = type && typeToString(type, declaration); + return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined; - if (!typeString) { - return undefined; - } + default: + throw Debug.fail(String(errorCode)); + } + } - return createCodeActions(declaration.name.getText(), declaration.name.getEnd(), `: ${typeString}`); + function isAllowedTokenKind(kind: SyntaxKind): boolean { + switch (kind) { + case SyntaxKind.Identifier: + case SyntaxKind.DotDotDotToken: + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + return true; + default: + return false; } + } + + function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(declaration.name)) return undefined; + const type = inferTypeForVariableFromUsage(declaration.name, sourceFile, program, cancellationToken); + return makeFix(declaration, declaration.name.getEnd(), type, program); + } - function getCodeActionForVariableUsage(token: Identifier) { - const symbol = checker.getSymbolAtLocation(token); - return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(symbol.valueDeclaration); + function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration { + switch (declaration.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + return true; + case SyntaxKind.FunctionExpression: + return !!(declaration as FunctionExpression).name; } + return false; + } - function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration { - switch (declaration.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - return true; - case SyntaxKind.FunctionExpression: - return !!(declaration as FunctionExpression).name; - } - return false; + function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { + return undefined; } - function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration): CodeAction[] { - if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { - return undefined; - } + const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || + map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name, sourceFile, program, cancellationToken)); + if (!types) return undefined; - const types = inferTypeForParametersFromUsage(containingFunction) || - map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name)); + const textChanges = mapDefinedIter(zipToIterator(containingFunction.parameters, types), ([parameter, type]) => + type && !parameter.type && !parameter.initializer ? makeChange(containingFunction, parameter.end, type, program) : undefined); - if (!types) { - return undefined; - } + return textChanges.length ? { declaration: parameterDeclaration, textChanges } : undefined; + } - const textChanges: TextChange[] = zipWith(containingFunction.parameters, types, (parameter, type) => { - if (type && !parameter.type && !parameter.initializer) { - const typeString = typeToString(type, containingFunction); - return typeString ? { - span: { start: parameter.end, length: 0 }, - newText: `: ${typeString}` - } : undefined; - } - }).filter(c => !!c); - - return textChanges.length ? [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_parameter_types_from_usage), [parameterDeclaration.name.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges - }] - }] : undefined; + function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + const setAccessorParameter = setAccessorDeclaration.parameters[0]; + if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) { + return undefined; } - function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration) { - const setAccessorParameter = setAccessorDeclaration.parameters[0]; - if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name) || - inferTypeForVariableFromUsage(setAccessorParameter.name); - const typeString = type && typeToString(type, containingFunction); - if (!typeString) { - return undefined; - } + const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, sourceFile, program, cancellationToken) || + inferTypeForVariableFromUsage(setAccessorParameter.name, sourceFile, program, cancellationToken); + return makeFix(setAccessorParameter, setAccessorParameter.name.getEnd(), type, program); + } - return createCodeActions(setAccessorDeclaration.name.getText(), setAccessorParameter.name.getEnd(), `: ${typeString}`); + function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(getAccessorDeclaration.name)) { + return undefined; } - function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration) { - if (!isIdentifier(getAccessorDeclaration.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name); - const typeString = type && typeToString(type, containingFunction); - if (!typeString) { - return undefined; - } - - const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken); - return createCodeActions(getAccessorDeclaration.name.getText(), closeParenToken.getEnd(), `: ${typeString}`); - } + const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name, sourceFile, program, cancellationToken); + const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken); + return makeFix(getAccessorDeclaration, closeParenToken.getEnd(), type, program); + } - function createCodeActions(name: string, start: number, typeString: string) { - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_type_of_0_from_usage), [name]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start, length: 0 }, - newText: typeString - }] - }] - }]; - } + function makeFix(declaration: Declaration, start: number, type: Type | undefined, program: Program): Fix | undefined { + return type && { declaration, textChanges: [makeChange(declaration, start, type, program)] }; + } - function getReferences(token: PropertyName | Token) { - const references = FindAllReferences.findReferencedSymbols( - program, - cancellationToken, - program.getSourceFiles(), - token.getSourceFile(), - token.getStart()); + function makeChange(declaration: Declaration, start: number, type: Type | undefined, program: Program): TextChange | undefined { + const typeString = type && typeToString(type, declaration, program.getTypeChecker()); + return typeString === undefined ? undefined : { span: createTextSpan(start, 0), newText: `: ${typeString}` }; + } - Debug.assert(!!references, "Found no references!"); - Debug.assert(references.length === 1, "Found more references than expected"); + function getReferences(token: PropertyName | Token, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Identifier[] { + const references = FindAllReferences.findReferencedSymbols( + program, + cancellationToken, + program.getSourceFiles(), + sourceFile, + token.getStart(sourceFile)); - return map(references[0].references, r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); - } + Debug.assert(!!references, "Found no references!"); + Debug.assert(references.length === 1, "Found more references than expected"); - function inferTypeForVariableFromUsage(token: Identifier) { - return InferFromReference.inferTypeFromReferences(getReferences(token), checker, cancellationToken); - } + return references[0].references.map(r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); + } - function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration) { - switch (containingFunction.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - const isConstructor = containingFunction.kind === SyntaxKind.Constructor; - const searchToken = isConstructor ? - >getFirstChildOfKind(containingFunction, sourceFile, SyntaxKind.ConstructorKeyword) : - containingFunction.name; - if (searchToken) { - return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken), containingFunction, checker, cancellationToken); - } - } - } + function inferTypeForVariableFromUsage(token: Identifier, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Type | undefined { + return InferFromReference.inferTypeFromReferences(getReferences(token, sourceFile, program, cancellationToken), program.getTypeChecker(), cancellationToken); + } - function getTypeAccessiblityWriter() { - if (!writer) { - let str = ""; - let typeIsAccessible = true; - - const writeText: (text: string) => void = text => str += text; - writer = { - string: () => typeIsAccessible ? str : undefined, - writeKeyword: writeText, - writeOperator: writeText, - writePunctuation: writeText, - writeSpace: writeText, - writeStringLiteral: writeText, - writeParameter: writeText, - writeProperty: writeText, - writeSymbol: writeText, - writeLine: () => str += " ", - increaseIndent: noop, - decreaseIndent: noop, - clear: () => { str = ""; typeIsAccessible = true; }, - trackSymbol: (symbol, declaration, meaning) => { - if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { - typeIsAccessible = false; - } - }, - reportInaccessibleThisError: () => { typeIsAccessible = false; }, - reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; }, - reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; } - }; - } - writer.clear(); - return writer; + function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Type[] | undefined { + switch (containingFunction.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + const isConstructor = containingFunction.kind === SyntaxKind.Constructor; + const searchToken = isConstructor ? + >getFirstChildOfKind(containingFunction, sourceFile, SyntaxKind.ConstructorKeyword) : + containingFunction.name; + if (searchToken) { + return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, sourceFile, program, cancellationToken), containingFunction, program.getTypeChecker(), cancellationToken); + } } + } - function typeToString(type: Type, enclosingDeclaration: Declaration) { - const writer = getTypeAccessiblityWriter(); - checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); - return writer.string(); - } + function getTypeAccessiblityWriter(checker: TypeChecker): StringSymbolWriter { + let str = ""; + let typeIsAccessible = true; + + const writeText: (text: string) => void = text => str += text; + return { + string: () => typeIsAccessible ? str : undefined, + writeKeyword: writeText, + writeOperator: writeText, + writePunctuation: writeText, + writeSpace: writeText, + writeStringLiteral: writeText, + writeParameter: writeText, + writeProperty: writeText, + writeSymbol: writeText, + writeLine: () => writeText(" "), + increaseIndent: noop, + decreaseIndent: noop, + clear: () => { str = ""; typeIsAccessible = true; }, + trackSymbol: (symbol, declaration, meaning) => { + if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + typeIsAccessible = false; + } + }, + reportInaccessibleThisError: () => { typeIsAccessible = false; }, + reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; }, + reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; } + }; + } - function getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) { - for (const child of node.getChildren(sourcefile)) { - if (child.kind === kind) return child; - } - return undefined; - } + function typeToString(type: Type, enclosingDeclaration: Declaration, checker: TypeChecker): string { + const writer = getTypeAccessiblityWriter(checker); + checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); + return writer.string(); } namespace InferFromReference { diff --git a/src/services/services.ts b/src/services/services.ts index 0d5e468031231..2cd6484e47319 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1880,7 +1880,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); @@ -1893,6 +1893,15 @@ namespace ts { }); } + function getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const newLineCharacter = getNewLineOrDefaultFromHost(host); + const formatContext = formatting.getFormatContext(formatOptions); + + return codefix.getAllFixes({ groupId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + } + function applyCodeActionCommand(action: CodeActionCommand): Promise; function applyCodeActionCommand(action: CodeActionCommand[]): Promise; function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -2187,6 +2196,7 @@ namespace ts { isValidBraceCompletionAtPosition, getSpanOfEnclosingComment, getCodeFixesAtPosition, + getCombinedCodeFix, applyCodeActionCommand, getEmitOutput, getNonBoundSourceFile, diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 7c4e25537efbe..2572f3905e120 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -194,6 +194,7 @@ namespace ts.textChanges { export class ChangeTracker { private changes: Change[] = []; private readonly newLineCharacter: string; + private readonly deletedNodesInLists: true[] = []; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. public static fromContext(context: TextChangesContext): ChangeTracker { return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext); @@ -220,14 +221,14 @@ namespace ts.textChanges { public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) { const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, node, options); - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } }); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); return this; } public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } }); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); return this; } @@ -245,6 +246,9 @@ namespace ts.textChanges { this.deleteNode(sourceFile, node); return this; } + const id = getNodeId(node); + Debug.assert(!this.deletedNodesInLists[id], "Deleting a node twice"); + this.deletedNodesInLists[id] = true; if (index !== containingList.length - 1) { const nextToken = getTokenAtPosition(sourceFile, node.end, /*includeJsDocComment*/ false); if (nextToken && isSeparator(node, nextToken)) { @@ -258,9 +262,17 @@ namespace ts.textChanges { } } else { - const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false); - if (previousToken && isSeparator(node, previousToken)) { - this.deleteNodeRange(sourceFile, previousToken, node); + const prev = containingList[index - 1]; + if (this.deletedNodesInLists[getNodeId(prev)]) { + const pos = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const end = getAdjustedEndPosition(sourceFile, node, {}); + this.deleteRange(sourceFile, { pos, end }); + } + else { + const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false); + if (previousToken && isSeparator(node, previousToken)) { + this.deleteNodeRange(sourceFile, previousToken, node); + } } } return this; diff --git a/src/services/types.ts b/src/services/types.ts index e74b830cc45c9..81f3e86650384 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -294,7 +294,8 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; + getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -409,6 +410,16 @@ namespace ts { commands?: CodeActionCommand[]; } + export interface CodeFix extends CodeAction { + /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ + groupId: {} | undefined; + } + + export interface CodeActionAll { + changes: FileTextChanges[]; + commands: CodeActionCommand[] | undefined; + } + // Publicly, this type is just `{}`. Internally it is a union of all the actions we use. // See `commands?: {}[]` in protocol.ts export type CodeActionCommand = InstallPackageAction; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e76fcd9349276..44a5f0686fe23 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1099,6 +1099,32 @@ namespace ts { return !seen[id] && (seen[id] = true); }; } + + /** Add a value to a set, and return true if it wasn't already present. */ + export function addToSeenIds(seen: true[], key: number): boolean { + if (seen[key]) { + return false; + } + seen[key] = true; + return true; + } + + /** Add a value to a set, and return true if it wasn't already present. */ + export function addToSeenStrings(seen: Map, key: string): boolean { + if (seen.has(key)) { + return false; + } + seen.set(key, true); + return true; + } + + export function singleElementArray(t: T | undefined): T[] { + return t === undefined ? undefined : [t]; + } + + export function getFirstChildOfKind(node: Node, sourceFile: SourceFile, kind: SyntaxKind): Node | undefined { + return find(node.getChildren(sourceFile), c => c.kind === kind); + } } // Display-part writer helpers diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c01647f7b4eff..d407d054da9f6 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -660,30 +660,30 @@ declare namespace ts { } interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -1279,7 +1279,7 @@ declare namespace ts { } interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } @@ -3980,7 +3980,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; + getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4072,6 +4073,14 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFix extends CodeAction { + /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ + groupId: {} | undefined; + } + interface CodeActionAll { + changes: FileTextChanges[]; + commands: CodeActionCommand[] | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } @@ -4912,6 +4921,7 @@ declare namespace ts.server.protocol { DocCommentTemplate = "docCommentTemplate", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", + GetCombinedCodeFix = "getCombinedCodeFix", ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -5259,6 +5269,10 @@ declare namespace ts.server.protocol { command: CommandTypes.GetCodeFixes; arguments: CodeFixRequestArgs; } + interface GetCombinedCodeFixRequest extends Request { + command: CommandTypes.GetCombinedCodeFix; + arguments: GetCombinedCodeFixRequestArgs; + } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -5292,6 +5306,9 @@ declare namespace ts.server.protocol { */ errorCodes?: number[]; } + interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { + groupId: {}; + } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; @@ -6034,7 +6051,7 @@ declare namespace ts.server.protocol { } interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeFix[]; } interface CodeAction { /** Description of the code action to display in the UI of the editor */ @@ -6044,6 +6061,14 @@ declare namespace ts.server.protocol { /** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */ commands?: {}[]; } + interface CodeActionAll { + changes: FileCodeEdits[]; + commands: {}[] | undefined; + } + interface CodeFix extends CodeAction { + /** If present, one may call 'getAllCodeFixesInGroup' with this groupId. */ + groupId: {} | undefined; + } /** * Format and format on key response message. */ @@ -7061,10 +7086,12 @@ declare namespace ts.server { private getApplicableRefactors(args); private getEditsForRefactor(args, simplifiedResult); private getCodeFixes(args, simplifiedResult); + private getCombinedCodeFix(args, simplifiedResult); private applyCodeActionCommand(commandName, requestSeq, args); private getStartAndEndPosition(args, scriptInfo); private mapCodeAction({description, changes: unmappedChanges, commands}, scriptInfo); private mapTextChangesToCodeEdits(project, textChanges); + private mapTextChangesToCodeEditsUsingScriptinfo(textChanges, scriptInfo); private convertTextChangeToCodeEdit(change, scriptInfo); private getBraceMatching(args, simplifiedResult); private getDiagnosticsForProject(next, delay, fileName); diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 1e843eee60d93..4e627422c0f78 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -660,30 +660,30 @@ declare namespace ts { } interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body: FunctionBody; } type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -1279,7 +1279,7 @@ declare namespace ts { } interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } @@ -3980,7 +3980,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; + getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4072,6 +4073,14 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFix extends CodeAction { + /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ + groupId: {} | undefined; + } + interface CodeActionAll { + changes: FileTextChanges[]; + commands: CodeActionCommand[] | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } diff --git a/tests/cases/fourslash/codeFixAddForgottenDecoratorCall01.ts b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator01.ts similarity index 100% rename from tests/cases/fourslash/codeFixAddForgottenDecoratorCall01.ts rename to tests/cases/fourslash/codeFixAddMissingInvocationForDecorator01.ts diff --git a/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts new file mode 100644 index 0000000000000..23c5e0cef649e --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts @@ -0,0 +1,23 @@ +/// + +////declare function foo(): (...args: any[]) => void; +////class C { +//// @foo +//// bar() {} +//// +//// @foo +//// baz() {} +////} + +verify.codeFixAll({ + groupId: "addMissingInvocationForDecorator", + newFileContent: +`declare function foo(): (...args: any[]) => void; +class C { + @foo() + bar() {} + + @foo() + baz() {} +}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all.ts b/tests/cases/fourslash/codeFixAddMissingMember_all.ts new file mode 100644 index 0000000000000..d21ee65ead4e8 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember_all.ts @@ -0,0 +1,26 @@ +/// + +////class C { +//// method() { +//// this.x = 0; +//// this.y(); +//// this.x = ""; +//// } +////} + +verify.codeFixAll({ + groupId: "addMissingMember", + newFileContent: + // TODO: GH#18445 +`class C { + x: number;\r + y(): any {\r + throw new Error("Method not implemented.");\r + }\r + method() { + this.x = 0; + this.y(); + this.x = ""; + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts new file mode 100644 index 0000000000000..1143780706d43 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts @@ -0,0 +1,32 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: /a.js +////class C { +//// constructor() {} +//// method() { +//// this.x; +//// this.y(); +//// this.x; +//// } +////} + +verify.codeFixAll({ + groupId: "addMissingMember", + newFileContent: + // TODO: GH#18445 GH#20073 +`class C { + y() {\r + throw new Error("Method not implemented.");\r + }\r + constructor() {this.x = undefined;\r +} + method() { + this.x; + this.y(); + this.x; + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_all.ts b/tests/cases/fourslash/codeFixCannotFindModule_all.ts new file mode 100644 index 0000000000000..9477228549ef1 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_all.ts @@ -0,0 +1,31 @@ +/// + +// @moduleResolution: node +// @noImplicitAny: true + +// @Filename: /node_modules/abs/index.js +////not read + +// @Filename: /node_modules/zap/index.js +////not read + +// @Filename: /a.ts +/////**/import * as abs from "abs"; +////import * as zap from "zap"; + +test.setTypesRegistry({ + "abs": undefined, + "zap": undefined, +}); + +goTo.marker(); + +verify.codeFixAll({ + groupId: "fixCannotFindModule", + commands: [ + { packageName: "@types/abs", file: "/a.ts", type: "install package" }, + { packageName: "@types/zap", file: "/a.ts", type: "install package" }, + ], + newFileContent: `import * as abs from "abs"; +import * as zap from "zap";` // unchanged +}); diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts new file mode 100644 index 0000000000000..e96905490d7f1 --- /dev/null +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts @@ -0,0 +1,7 @@ +// @strict: true +////function f(a: ?number, b: string!) {} + +verify.codeFixAll({ + groupId: "fixJSDocTypes_plain", + newFileContent: "function f(a: number | null, b: string) {}", +}) diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts new file mode 100644 index 0000000000000..7757451ee799e --- /dev/null +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts @@ -0,0 +1,7 @@ +// @strict: true +////function f(a: ?number, b: string!) {} + +verify.codeFixAll({ + groupId: "fixJSDocTypes_nullable", + newFileContent: "function f(a: number | null | undefined, b: string) {}", +}) diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts new file mode 100644 index 0000000000000..01443e39eaffe --- /dev/null +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts @@ -0,0 +1,24 @@ +/// + +////abstract class A { +//// abstract m(): void; +////} +////class B extends A {} +////class C extends A {} + +verify.codeFixAll({ + groupId: "fixClassDoesntImplementInheritedAbstractMember", + // TODO: GH#20073 GH#18445 + newFileContent: +`abstract class A { + abstract m(): void; +} +class B extends A {m(): void {\r + throw new Error("Method not implemented.");\r +}\r +} +class C extends A {m(): void {\r + throw new Error("Method not implemented.");\r +}\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterface_all.ts b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts new file mode 100644 index 0000000000000..74475da749e9c --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts @@ -0,0 +1,25 @@ +/// + +////interface I { i(): void; } +////interface J { j(): void; } +////class C implements I, J {} +////class D implements J {} + +verify.codeFixAll({ + groupId: "fixClassIncorrectlyImplementsInterface", + // TODO: GH#20073 GH#18445 + newFileContent: +`interface I { i(): void; } +interface J { j(): void; } +class C implements I, J {i(): void {\r + throw new Error("Method not implemented.");\r +}\r +j(): void {\r + throw new Error("Method not implemented.");\r +}\r +} +class D implements J {j(): void {\r + throw new Error("Method not implemented.");\r +}\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixSuperAfterThis.ts b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess.ts similarity index 100% rename from tests/cases/fourslash/codeFixSuperAfterThis.ts rename to tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess.ts diff --git a/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts new file mode 100644 index 0000000000000..80f2bffddb333 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts @@ -0,0 +1,33 @@ +/// + +////class C extends Object { +//// constructor() { +//// this; +//// this; +//// super(); +//// } +////} +////class D extends Object { +//// constructor() { +//// this; +//// super(); +//// } +////} + +verify.codeFixAll({ + groupId: "classSuperMustPrecedeThisAccess", + newFileContent: `class C extends Object { + constructor() { + super();\r + this; + this; + } +} +class D extends Object { + constructor() { + super();\r + this; + } +}`, +}); + diff --git a/tests/cases/fourslash/codeFixSuperCallWithThisInside.ts b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_callWithThisInside.ts similarity index 100% rename from tests/cases/fourslash/codeFixSuperCallWithThisInside.ts rename to tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_callWithThisInside.ts diff --git a/tests/cases/fourslash/codeFixSuperCall.ts b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall.ts similarity index 100% rename from tests/cases/fourslash/codeFixSuperCall.ts rename to tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall.ts diff --git a/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts new file mode 100644 index 0000000000000..8091a7e1360c9 --- /dev/null +++ b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts @@ -0,0 +1,21 @@ +/// + +////class C extends Object { +//// constructor() {} +////} +////class D extends Object { +//// constructor() {} +////} + +verify.codeFixAll({ + groupId: "constructorForDerivedNeedSuperCall", + // TODO: GH#18445 GH#20073 + newFileContent: `class C extends Object { + constructor() {super();\r +} +} +class D extends Object { + constructor() {super();\r +} +}`, +}); diff --git a/tests/cases/fourslash/codeFixReplaceQualifiedNameWithIndexedAccessType01.ts b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType01.ts similarity index 100% rename from tests/cases/fourslash/codeFixReplaceQualifiedNameWithIndexedAccessType01.ts rename to tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType01.ts diff --git a/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts new file mode 100644 index 0000000000000..2b9a969ad3146 --- /dev/null +++ b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts @@ -0,0 +1,17 @@ +/// + +////interface Foo { +//// bar: string; +////} +////const x: Foo.bar = ""; +////const y: Foo.bar = ""; + +verify.codeFixAll({ + groupId: "correctQualifiedNameToIndexedAccessType", + newFileContent: +`interface Foo { + bar: string; +} +const x: Foo["bar"] = ""; +const y: Foo["bar"] = "";`, +}); diff --git a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts new file mode 100644 index 0000000000000..14d947f61d037 --- /dev/null +++ b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts @@ -0,0 +1,20 @@ +/// + +// @allowjs: true +// @checkJs: true +// @noEmit: true + +// @Filename: a.js +////let x = ""; +////x = 1; +////x = true; + +verify.codeFixAll({ + groupId: "disableJsDiagnostics", + newFileContent: +`let x = ""; +// @ts-ignore\r +x = 1; +// @ts-ignore\r +x = true;`, +}); diff --git a/tests/cases/fourslash/codeFixChangeExtendsToImplements.ts b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements.ts similarity index 100% rename from tests/cases/fourslash/codeFixChangeExtendsToImplements.ts rename to tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements.ts diff --git a/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts new file mode 100644 index 0000000000000..c04458d6f2273 --- /dev/null +++ b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts @@ -0,0 +1,12 @@ +/// + +////interface I {} +////class C extends I {} +////class D extends I {} + +verify.codeFixAll({ + groupId: "extendsInterfaceBecomesImplements", + newFileContent: `interface I {} +class C implements I {} +class D implements I {}`, +}); diff --git a/tests/cases/fourslash/codeFixAddForgottenThis01.ts b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess01.ts similarity index 100% rename from tests/cases/fourslash/codeFixAddForgottenThis01.ts rename to tests/cases/fourslash/codeFixForgottenThisPropertyAccess01.ts diff --git a/tests/cases/fourslash/codeFixAddForgottenThis02.ts b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess02.ts similarity index 100% rename from tests/cases/fourslash/codeFixAddForgottenThis02.ts rename to tests/cases/fourslash/codeFixForgottenThisPropertyAccess02.ts diff --git a/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts new file mode 100644 index 0000000000000..646621487f2b0 --- /dev/null +++ b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// foo: number; +//// m() { +//// foo; +//// foo; +//// } +////} + +verify.codeFixAll({ + groupId: "forgottenThisPropertyAccess", + newFileContent: +`class C { + foo: number; + m() { + this.foo; + this.foo; + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixInferFromUsage_all.ts b/tests/cases/fourslash/codeFixInferFromUsage_all.ts new file mode 100644 index 0000000000000..e5502363d69dd --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsage_all.ts @@ -0,0 +1,25 @@ +/// + +// @noImplicitAny: true + +////function f(x, y) { +//// x += 0; +//// y += ""; +////} +//// +////function g(z) { +//// return z * 2; +////} + +verify.codeFixAll({ + groupId: "inferFromUsage", + newFileContent: +`function f(x: number, y: string) { + x += 0; + y += ""; +} + +function g(z: number) { + return z * 2; +}`, +}); diff --git a/tests/cases/fourslash/codeFixCorrectSpelling1.ts b/tests/cases/fourslash/codeFixSpelling1.ts similarity index 100% rename from tests/cases/fourslash/codeFixCorrectSpelling1.ts rename to tests/cases/fourslash/codeFixSpelling1.ts diff --git a/tests/cases/fourslash/codeFixCorrectSpelling2.ts b/tests/cases/fourslash/codeFixSpelling2.ts similarity index 100% rename from tests/cases/fourslash/codeFixCorrectSpelling2.ts rename to tests/cases/fourslash/codeFixSpelling2.ts diff --git a/tests/cases/fourslash/codeFixCorrectSpelling3.ts b/tests/cases/fourslash/codeFixSpelling3.ts similarity index 100% rename from tests/cases/fourslash/codeFixCorrectSpelling3.ts rename to tests/cases/fourslash/codeFixSpelling3.ts diff --git a/tests/cases/fourslash/codeFixCorrectSpelling4.ts b/tests/cases/fourslash/codeFixSpelling4.ts similarity index 100% rename from tests/cases/fourslash/codeFixCorrectSpelling4.ts rename to tests/cases/fourslash/codeFixSpelling4.ts diff --git a/tests/cases/fourslash/codeFixSpelling_all.ts b/tests/cases/fourslash/codeFixSpelling_all.ts new file mode 100644 index 0000000000000..0fc7e50b95bd1 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpelling_all.ts @@ -0,0 +1,15 @@ +/// + +////function f(s: string) { +//// s.toStrang(); +//// s.toStrong(); +////} + +verify.codeFixAll({ + groupId: "fixSpelling", + newFileContent: +`function f(s: string) { + s.toString(); + s.toString(); +}`, +}); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts new file mode 100644 index 0000000000000..3a6f94e2c12f3 --- /dev/null +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts @@ -0,0 +1,15 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true + +////function f(a, b) { +//// const x = 0; +////} + +verify.codeFixAll({ + groupId: "unusedIdentifier_delete", + newFileContent: +`function f() { +}`, +}); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts new file mode 100644 index 0000000000000..8f67ff5d23415 --- /dev/null +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts @@ -0,0 +1,16 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true + +////function f(a, b) { +//// const x = 0; // Can't be prefixed, ignored +////} + +verify.codeFixAll({ + groupId: "unusedIdentifier_prefix", + newFileContent: +`function f(_a, _b) { + const x = 0; // Can't be prefixed, ignored +}`, +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b38a7f453bcf4..615101ae4acb6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -171,7 +171,7 @@ declare namespace FourSlashInterface { errorCode?: number, index?: number, }); - codeFixAvailable(options: Array<{ description: string, actions: Array<{ type: string, data: {} }> }>): void; + codeFixAvailable(options: Array<{ description: string, actions?: Array<{ type: string, data: {} }>, commands?: {}[] }>): void; applicableRefactorAvailableAtMarker(markerName: string): void; codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; applicableRefactorAvailableForRange(): void; @@ -283,6 +283,7 @@ declare namespace FourSlashInterface { docCommentTemplateAt(markerName: string | FourSlashInterface.Marker, expectedOffset: number, expectedText: string): void; noDocCommentTemplateAt(markerName: string | FourSlashInterface.Marker): void; rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void; + codeFixAll(options: { groupId: string, newFileContent: string, commands?: {}[] }): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: FormatCodeOptions): void; rangeIs(expectedText: string, includeWhiteSpace?: boolean): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void; diff --git a/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts b/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts index 634561a5c65f5..1c9e9307fcd55 100644 --- a/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts +++ b/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts @@ -1,12 +1,15 @@ /// // @noUnusedLocals: true -//// class C1 { -//// [|constructor(private p1: string, public p2: boolean, public p3: any, p5) |] { p5; } -//// } +////class C1 { +//// constructor(private p1: string, public p2: boolean, public p3: any, p5) { p5; } +////} verify.codeFix({ description: "Prefix 'p1' with an underscore.", index: 1, - newRangeContent: "constructor(private _p1: string, public p2: boolean, public p3: any, p5)", + newFileContent: +`class C1 { + constructor(private _p1: string, public p2: boolean, public p3: any, p5) { p5; } +}`, }); diff --git a/tests/cases/fourslash/unusedParameterInFunction1AddUnderscore.ts b/tests/cases/fourslash/unusedParameterInFunction1AddUnderscore.ts index c248c5e1a94cc..e984b5482a97e 100644 --- a/tests/cases/fourslash/unusedParameterInFunction1AddUnderscore.ts +++ b/tests/cases/fourslash/unusedParameterInFunction1AddUnderscore.ts @@ -1,11 +1,10 @@ /// // @noUnusedParameters: true -////function [|greeter( x) |] { -////} +////function greeter(x) {} verify.codeFix({ description: "Prefix 'x' with an underscore.", index: 1, - newRangeContent: "greeter( _x)", + newFileContent: "function greeter(_x) {}", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda1AddUnderscore.ts b/tests/cases/fourslash/unusedParameterInLambda1AddUnderscore.ts index 1b05c66dd2fb5..36e74b2223e5d 100644 --- a/tests/cases/fourslash/unusedParameterInLambda1AddUnderscore.ts +++ b/tests/cases/fourslash/unusedParameterInLambda1AddUnderscore.ts @@ -1,13 +1,10 @@ /// -// @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return (x:number) => {} |] -//// } +////(x: number) => {} verify.codeFix({ description: "Prefix 'x' with an underscore.", index: 1, - newRangeContent: "return (_x:number) => {}", + newFileContent: "(_x: number) => {}", }); diff --git a/tests/cases/fourslash/unusedVariableInForLoop5FSAddUnderscore.ts b/tests/cases/fourslash/unusedVariableInForLoop5FSAddUnderscore.ts index 87783ff67fc0b..9f83c1f3d1c8f 100644 --- a/tests/cases/fourslash/unusedVariableInForLoop5FSAddUnderscore.ts +++ b/tests/cases/fourslash/unusedVariableInForLoop5FSAddUnderscore.ts @@ -1,13 +1,9 @@ /// // @noUnusedLocals: true -//// function f1 () { -//// [|for (const elem in ["a", "b", "c"]) |]{ -//// -//// } -//// } +////for (const elem in ["a", "b", "c"]) {} verify.codeFix({ description: "Prefix 'elem' with an underscore.", - newRangeContent: 'for (const _elem in ["a", "b", "c"])' + newFileContent: 'for (const _elem in ["a", "b", "c"]) {}', }); diff --git a/tests/cases/fourslash/unusedVariableInForLoop6FSAddUnderscore.ts b/tests/cases/fourslash/unusedVariableInForLoop6FSAddUnderscore.ts index 9f8c7b19f99d8..66b004b422af7 100644 --- a/tests/cases/fourslash/unusedVariableInForLoop6FSAddUnderscore.ts +++ b/tests/cases/fourslash/unusedVariableInForLoop6FSAddUnderscore.ts @@ -1,15 +1,10 @@ /// // @noUnusedLocals: true -//// function f1 () { -//// for ([|const elem of |]["a", "b", "c"]) { -//// -//// } -//// } +////for ([|const elem of |]["a", "b", "c"]) {} verify.codeFix({ description: "Prefix 'elem' with an underscore.", index: 1, - newRangeContent: "const _elem of" + newFileContent: 'for (const _elem of ["a", "b", "c"]) {}', }); - From be19542dd650d12c811f9bea4af438fbe1eb1856 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 30 Nov 2017 08:45:13 -0800 Subject: [PATCH 02/11] Rename things --- src/harness/fourslash.ts | 10 +++++----- src/server/client.ts | 2 +- src/server/protocol.ts | 14 ++++++++----- src/server/session.ts | 2 +- src/services/codeFixProvider.ts | 14 ++++++------- .../addMissingInvocationForDecorator.ts | 8 ++++---- ...correctQualifiedNameToIndexedAccessType.ts | 8 ++++---- .../codefixes/disableJsDiagnostics.ts | 12 +++++------ src/services/codefixes/fixAddMissingMember.ts | 16 +++++++-------- src/services/codefixes/fixCannotFindModule.ts | 8 ++++---- ...sDoesntImplementInheritedAbstractMember.ts | 8 ++++---- .../fixClassIncorrectlyImplementsInterface.ts | 8 ++++---- .../fixClassSuperMustPrecedeThisAccess.ts | 8 ++++---- .../fixConstructorForDerivedNeedSuperCall.ts | 8 ++++---- .../fixExtendsInterfaceBecomesImplements.ts | 8 ++++---- .../fixForgottenThisPropertyAccess.ts | 8 ++++---- src/services/codefixes/fixJSDocTypes.ts | 20 +++++++++---------- src/services/codefixes/fixSpelling.ts | 8 ++++---- src/services/codefixes/fixUnusedIdentifier.ts | 20 +++++++++---------- src/services/codefixes/importFixes.ts | 6 +++--- src/services/codefixes/inferFromUsage.ts | 8 ++++---- src/services/services.ts | 4 ++-- src/services/types.ts | 6 +++--- .../reference/api/tsserverlibrary.d.ts | 17 +++++++++------- tests/baselines/reference/api/typescript.d.ts | 6 +++--- ...FixAddMissingInvocationForDecorator_all.ts | 2 +- .../fourslash/codeFixAddMissingMember_all.ts | 2 +- .../codeFixAddMissingMember_all_js.ts | 2 +- .../fourslash/codeFixCannotFindModule_all.ts | 2 +- .../fourslash/codeFixChangeJSDocSyntax_all.ts | 4 +++- .../codeFixChangeJSDocSyntax_all_nullable.ts | 4 +++- .../codeFixClassExtendAbstractMethod_all.ts | 2 +- .../codeFixClassImplementInterface_all.ts | 2 +- ...eFixClassSuperMustPrecedeThisAccess_all.ts | 2 +- ...xConstructorForDerivedNeedSuperCall_all.ts | 2 +- ...ectQualifiedNameToIndexedAccessType_all.ts | 2 +- .../codeFixDisableJsDiagnosticsInFile_all.ts | 2 +- ...ixExtendsInterfaceBecomesImplements_all.ts | 2 +- .../codeFixForgottenThisPropertyAccess_all.ts | 2 +- .../fourslash/codeFixInferFromUsage_all.ts | 2 +- tests/cases/fourslash/codeFixSpelling_all.ts | 2 +- .../codeFixUnusedIdentifier_all_delete.ts | 2 +- .../codeFixUnusedIdentifier_all_prefix.ts | 2 +- tests/cases/fourslash/fourslash.ts | 2 +- 44 files changed, 145 insertions(+), 134 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index d5d0cb44b41f8..fcf80230d4358 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2425,10 +2425,10 @@ Actual: ${stringify(fullActual)}`); } public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void { - const { groupId, newFileContent } = options; - const groupIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.groupId); - ts.Debug.assert(ts.contains(groupIds, groupId), "No available code fix has that group id.", () => `Expected '${groupId}'. Available group ids: ${groupIds}`); - const { changes, commands } = this.languageService.getCombinedCodeFix(this.activeFile.fileName, groupId, this.formatCodeSettings); + const { actionId, newFileContent } = options; + const actionIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.actionId); + ts.Debug.assert(ts.contains(actionIds, actionId), "No available code fix has that group id.", () => `Expected '${actionId}'. Available action ids: ${actionIds}`); + const { changes, commands } = this.languageService.getCombinedCodeFix(this.activeFile.fileName, actionId, this.formatCodeSettings); assert.deepEqual(commands, options.commands); this.applyChanges(changes); this.verifyCurrentFileContent(newFileContent); @@ -4595,7 +4595,7 @@ namespace FourSlashInterface { } export interface VerifyCodeFixAllOptions { - groupId: string; + actionId: string; newFileContent: string; commands: ReadonlyArray<{}>; } diff --git a/src/server/client.ts b/src/server/client.ts index 2b44a82b8e71c..4e3fa91a577b2 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -558,7 +558,7 @@ namespace ts.server { const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - return response.body.map(({ description, changes, groupId }) => ({ description, changes: this.convertChanges(changes, file), groupId })); + return response.body.map(({ description, changes, actionId }) => ({ description, changes: this.convertChanges(changes, file), actionId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0246f532cada4..84e154a9a1703 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -103,7 +103,7 @@ namespace ts.server.protocol { GetCodeFixesFull = "getCodeFixes-full", GetCombinedCodeFix = "getCombinedCodeFix", /* @internal */ - GetCombinedCodeFixFull = "getCombinedCodeFix", + GetCombinedCodeFixFull = "getCombinedCodeFix-full", ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", @@ -541,6 +541,10 @@ namespace ts.server.protocol { arguments: GetCombinedCodeFixRequestArgs; } + export interface GetCombinedCodeFixResponse extends Response { + body: CodeActionAll; + } + export interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -594,7 +598,7 @@ namespace ts.server.protocol { } export interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { - groupId: {}; + actionId: {}; } export interface ApplyCodeActionCommandRequestArgs { @@ -1594,12 +1598,12 @@ namespace ts.server.protocol { export interface CodeActionAll { changes: FileCodeEdits[]; - commands: {}[] | undefined; + commands?: {}[]; } export interface CodeFix extends CodeAction { - /** If present, one may call 'getAllCodeFixesInGroup' with this groupId. */ - groupId: {} | undefined; + /** If present, one may call 'getAllCodeFixesInGroup' with this actionId. */ + actionId?: {}; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 2f19bc731714c..2699fff62c5a9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1570,7 +1570,7 @@ namespace ts.server { private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeActionAll | CodeActionAll { const { file, project } = this.getFileAndProject(args); const formatOptions = this.projectService.getFormatCodeOptions(file); - const res = project.getLanguageService().getCombinedCodeFix(file, args.groupId, formatOptions); + const res = project.getLanguageService().getCombinedCodeFix(file, args.actionId, formatOptions); if (simplifiedResult) { return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; } diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 12c7a84af5e7d..99293fd257e1c 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -3,8 +3,8 @@ namespace ts { export interface CodeFixRegistration { errorCodes: number[]; getCodeActions(context: CodeFixContext): CodeFix[] | undefined; - groupIds: string[]; - fixAllInGroup(context: CodeFixAllContext): CodeActionAll; + actionIds: string[]; + getAllCodeActions(context: CodeFixAllContext): CodeActionAll; } export interface CodeFixContextBase extends textChanges.TextChangesContext { @@ -15,7 +15,7 @@ namespace ts { } export interface CodeFixAllContext extends CodeFixContextBase { - groupId: {}; + actionId: {}; } export interface CodeFixContext extends CodeFixContextBase { @@ -36,8 +36,8 @@ namespace ts { } fixes.push(codeFix); } - if (codeFix.groupIds) { - for (const gid of codeFix.groupIds) { + if (codeFix.actionIds) { + for (const gid of codeFix.actionIds) { Debug.assert(!groups.has(gid)); groups.set(gid, codeFix); } @@ -70,8 +70,8 @@ namespace ts { } export function getAllFixes(context: CodeFixAllContext): CodeActionAll { - // Currently groupId is always a string. - return groups.get(cast(context.groupId, isString)).fixAllInGroup!(context); + // Currently actionId is always a string. + return groups.get(cast(context.actionId, isString)).getAllCodeActions!(context); } function createCodeActionAll(changes: FileTextChanges[], commands?: CodeActionCommand[]): CodeActionAll { diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index 3977ccab28e07..17549e73d5631 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -1,15 +1,15 @@ /* @internal */ namespace ts.codefix { - const groupId = "addMissingInvocationForDecorator"; + const actionId = "addMissingInvocationForDecorator"; const errorCodes = [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code]; registerCodeFix({ errorCodes, getCodeActions: (context) => { const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, groupId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)), + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)), }); function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) { diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index e7ffe7ed56e1b..352176f111d84 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "correctQualifiedNameToIndexedAccessType"; + const actionId = "correctQualifiedNameToIndexedAccessType"; const errorCodes = [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code]; registerCodeFix({ errorCodes, @@ -9,10 +9,10 @@ namespace ts.codefix { if (!qualifiedName) return undefined; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); - return [{ description, changes, groupId }]; + return [{ description, changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: (context) => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionId], + getAllCodeActions: (context) => codeFixAll(context, errorCodes, (changes, diag) => { const q = getQualifiedName(diag.file, diag.start); if (q) { doChange(changes, diag.file, q); diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index a1f642ab57e7b..61d11515b8f9d 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "disableJsDiagnostics"; + const actionId = "disableJsDiagnostics"; const errorCodes = mapDefined(Object.keys(Diagnostics), key => { const diag = (Diagnostics as MapLike)[key]; return diag.category === DiagnosticCategory.Error ? diag.code : undefined; @@ -18,7 +18,7 @@ namespace ts.codefix { return [{ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], - groupId, + actionId, }, { description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), @@ -29,12 +29,12 @@ namespace ts.codefix { }, newText: `// @ts-nocheck${newLineCharacter}` }])], - // groupId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. - groupId: undefined, + // actionId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. + actionId: undefined, }]; }, - groupIds: [groupId], // No point applying as a group, doing it once will fix all errors - fixAllInGroup: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + actionIds: [actionId], // No point applying as a group, doing it once will fix all errors + getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { if (err.start !== undefined) { changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 3287597bb7aba..3c05750c77455 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -4,7 +4,7 @@ namespace ts.codefix { Diagnostics.Property_0_does_not_exist_on_type_1.code, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, ]; - const groupId = "addMissingMember"; + const actionId = "addMissingMember"; registerCodeFix({ errorCodes, getCodeActions(context) { @@ -17,8 +17,8 @@ namespace ts.codefix { getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classOpenBrace, token, classDeclaration, makeStatic); return concatenate(singleElementArray(methodCodeAction), addMember); }, - groupIds: [groupId], - fixAllInGroup: context => { + actionIds: [actionId], + getAllCodeActions: context => { const seenNames = createMap(); return codeFixAll(context, errorCodes, (changes, diag) => { const { newLineCharacter, program } = context; @@ -100,7 +100,7 @@ namespace ts.codefix { const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic, context.newLineCharacter)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); - return { description, changes, groupId }; + return { description, changes, actionId }; } function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean, newLineCharacter: string): void { @@ -146,7 +146,7 @@ namespace ts.codefix { function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFix { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classOpenBrace, tokenName, typeNode, makeStatic, context.newLineCharacter)); - return { description, changes, groupId }; + return { description, changes, actionId }; } function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, tokenName: string, typeNode: TypeNode, makeStatic: boolean, newLineCharacter: string): void { @@ -178,14 +178,14 @@ namespace ts.codefix { typeNode); const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter })); - // No groupId here because code-fix-all currently only works on adding individual named properties. - return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, groupId: undefined }; + // No actionId here because code-fix-all currently only works on adding individual named properties. + return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, actionId: undefined }; } function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFix | undefined { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classOpenBrace, token, callExpression, context.newLineCharacter, makeStatic, inJs)); - return { description, changes, groupId }; + return { description, changes, actionId }; } function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classOpenBrace: Node, token: Identifier, callExpression: CallExpression, newLineCharacter: string, makeStatic: boolean, inJs: boolean) { diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 21dd8792ac6a2..6391849ed165e 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,14 +1,14 @@ /* @internal */ namespace ts.codefix { - const groupId = "fixCannotFindModule"; + const actionId = "fixCannotFindModule"; const errorCodes = [Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code]; registerCodeFix({ errorCodes, getCodeActions: context => [ - { groupId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) } + { actionId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) } ], - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (_, diag, commands) => { + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => { const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start)); if (pkg) { commands.push(getCommand(diag.file.fileName, pkg)); diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index bee7f20029554..3a8c407a9b235 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -4,17 +4,17 @@ namespace ts.codefix { Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code, ]; - const groupId = "fixClassDoesntImplementInheritedAbstractMember"; + const actionId = "fixClassDoesntImplementInheritedAbstractMember"; registerCodeFix({ errorCodes, getCodeActions(context) { const { program, sourceFile, span } = context; const changes = textChanges.ChangeTracker.with(context, t => addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), context.newLineCharacter, t)); - return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, groupId }]; + return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), context.newLineCharacter, changes); }), }); diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 83e15d34ae99d..c6fd457be4b24 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code]; - const groupId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? + const actionId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? registerCodeFix({ errorCodes, getCodeActions(context) { @@ -12,11 +12,11 @@ namespace ts.codefix { const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, newLineCharacter, t)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); - return { description, changes, groupId }; + return { description, changes, actionId }; }); }, - groupIds: [groupId], - fixAllInGroup(context) { + actionIds: [actionId], + getAllCodeActions(context) { const seenClassDeclarations: true[] = []; return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file!, diag.start!); diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 901b9b827402c..4dd5c72cf520d 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "classSuperMustPrecedeThisAccess"; + const actionId = "classSuperMustPrecedeThisAccess"; const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code]; registerCodeFix({ errorCodes, @@ -10,10 +10,10 @@ namespace ts.codefix { if (!nodes) return undefined; const { constructor, superCall } = nodes; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall, context.newLineCharacter)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, groupId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup(context) { + actionIds: [actionId], + getAllCodeActions(context) { const { newLineCharacter, sourceFile } = context; const seenClasses: true[] = []; // Ensure we only do this once per class. return codeFixAll(context, errorCodes, (changes, diag) => { diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 3d36071b4de73..46024b41cf6e9 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "constructorForDerivedNeedSuperCall"; + const actionId = "constructorForDerivedNeedSuperCall"; const errorCodes = [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code]; registerCodeFix({ errorCodes, @@ -8,10 +8,10 @@ namespace ts.codefix { const { sourceFile } = context; const ctr = getNode(sourceFile, context.span.start); const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr, context.newLineCharacter)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, groupId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, context.sourceFile, getNode(diag.file, diag.start!), context.newLineCharacter)), }); diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index d366a95439f68..a42d18ec20cda 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "extendsInterfaceBecomesImplements"; + const actionId = "extendsInterfaceBecomesImplements"; const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code]; registerCodeFix({ errorCodes, @@ -10,10 +10,10 @@ namespace ts.codefix { if (!nodes) return undefined; const { extendsToken, heritageClauses } = nodes; const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, groupId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start!); if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses); }), diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index fb6cf36e89fa1..15d0c061f835b 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "forgottenThisPropertyAccess"; + const actionId = "forgottenThisPropertyAccess"; const errorCodes = [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code]; registerCodeFix({ errorCodes, @@ -8,10 +8,10 @@ namespace ts.codefix { const { sourceFile } = context; const token = getNode(sourceFile, context.span.start); const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, groupId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { doChange(changes, context.sourceFile, getNode(diag.file, diag.start!)); }), }); diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts index c84b5aa52420d..68c0614497e92 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { - const groupIdPlain = "fixJSDocTypes_plain"; - const groupIdNullable = "fixJSDocTypes_nullable"; + const actionIdPlain = "fixJSDocTypes_plain"; + const actionIdNullable = "fixJSDocTypes_nullable"; const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code]; registerCodeFix({ errorCodes, @@ -12,32 +12,32 @@ namespace ts.codefix { if (!info) return undefined; const { typeNode, type } = info; const original = typeNode.getText(sourceFile); - const actions = [fix(type, groupIdPlain)]; + const actions = [fix(type, actionIdPlain)]; if (typeNode.kind === SyntaxKind.JSDocNullableType) { // for nullable types, suggest the flow-compatible `T | null | undefined` // in addition to the jsdoc/closure-compatible `T | null` - actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), groupIdNullable)); + actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), actionIdNullable)); } return actions; - function fix(type: Type, groupId: string): CodeFix { + function fix(type: Type, actionId: string): CodeFix { const newText = typeString(type, checker); return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]), changes: [createFileTextChanges(sourceFile.fileName, [createChange(typeNode, sourceFile, newText)])], - groupId, + actionId, }; } }, - groupIds: [groupIdPlain, groupIdNullable], - fixAllInGroup(context) { - const { groupId, program, sourceFile } = context; + actionIds: [actionIdPlain, actionIdNullable], + getAllCodeActions(context) { + const { actionId, program, sourceFile } = context; const checker = program.getTypeChecker(); return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { const info = getInfo(err.file, err.start!, checker); if (!info) return; const { typeNode, type } = info; - const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && groupId === groupIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; + const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && actionId === actionIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; changes.push(createChange(typeNode, sourceFile, typeString(fixedType, checker))); }); } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 2347cd8ff9cff..8dc3c418d20b1 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "fixSpelling"; + const actionId = "fixSpelling"; const errorCodes = [ Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, @@ -14,10 +14,10 @@ namespace ts.codefix { const { node, suggestion } = info; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); - return [{ description, changes, groupId }]; + return [{ description, changes, actionId }]; }, - groupIds: [groupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); if (info) doChange(changes, context.sourceFile, info.node, info.suggestion); }), diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index f468b82c38cea..0457469bc2278 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { - const prefixGroupId = "unusedIdentifier_prefix"; - const deleteGroupId = "unusedIdentifier_delete"; + const actionIdPrefix = "unusedIdentifier_prefix"; + const actionIdDelete = "unusedIdentifier_delete"; const errorCodes = [ Diagnostics._0_is_declared_but_its_value_is_never_read.code, Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code, @@ -16,32 +16,32 @@ namespace ts.codefix { const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); if (deletion.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); - result.push({ description, changes: deletion, groupId: deleteGroupId }); + result.push({ description, changes: deletion, actionId: actionIdDelete }); } const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, sourceFile, token)); if (prefix.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); - result.push({ description, changes: prefix, groupId: prefixGroupId }); + result.push({ description, changes: prefix, actionId: actionIdPrefix }); } return result; }, - groupIds: [prefixGroupId, deleteGroupId], - fixAllInGroup: context => codeFixAll(context, errorCodes, (changes, diag) => { + actionIds: [actionIdPrefix, actionIdDelete], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const { sourceFile } = context; const token = getToken(diag.file!, diag.start!); - switch (context.groupId) { - case prefixGroupId: + switch (context.actionId) { + case actionIdPrefix: if (isIdentifier(token) && canPrefix(token)) { tryPrefixDeclaration(changes, sourceFile, token); } break; - case deleteGroupId: + case actionIdDelete: tryDeleteDeclaration(changes, sourceFile, token); break; default: - Debug.fail(JSON.stringify(context.groupId)); + Debug.fail(JSON.stringify(context.actionId)); } }), }); diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index c204d0cd9178c..29b556b3f5791 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -11,8 +11,8 @@ namespace ts.codefix { ], getCodeActions: getImportCodeActions, // TODO: GH#20315 - groupIds: [], - fixAllInGroup: notImplemented, + actionIds: [], + getAllCodeActions: notImplemented, }); type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport"; @@ -158,7 +158,7 @@ namespace ts.codefix { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), changes, // TODO: GH#20315 - groupId: undefined, + actionId: undefined, kind, moduleSpecifier }; diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 4b53804f458d6..1c04e1fa9a6f6 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const groupId = "inferFromUsage"; + const actionId = "inferFromUsage"; const errorCodes = [ // Variable declarations Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, @@ -36,10 +36,10 @@ namespace ts.codefix { const { declaration, textChanges } = fix; const name = getNameOfDeclaration(declaration); const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name.getText()]); - return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], groupId }]; + return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], actionId }]; }, - groupIds: [groupId], - fixAllInGroup(context) { + actionIds: [actionId], + getAllCodeActions(context) { const { sourceFile, program, cancellationToken } = context; const seenFunctions: true[] = []; return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { diff --git a/src/services/services.ts b/src/services/services.ts index 2cd6484e47319..e4d503fbefe10 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1893,13 +1893,13 @@ namespace ts { }); } - function getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll { + function getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); - return codefix.getAllFixes({ groupId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getAllFixes({ actionId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); } function applyCodeActionCommand(action: CodeActionCommand): Promise; diff --git a/src/services/types.ts b/src/services/types.ts index 81f3e86650384..f4a15e100a4f8 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,7 +295,7 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; + getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -411,8 +411,8 @@ namespace ts { } export interface CodeFix extends CodeAction { - /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ - groupId: {} | undefined; + /** If present, one may call 'getCombinedCodeFix' with this actionId. */ + actionId: {} | undefined; } export interface CodeActionAll { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d407d054da9f6..648a05b461295 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3981,7 +3981,7 @@ declare namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; + getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4074,8 +4074,8 @@ declare namespace ts { commands?: CodeActionCommand[]; } interface CodeFix extends CodeAction { - /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ - groupId: {} | undefined; + /** If present, one may call 'applyAllCodeFixesInGroup' with this actionId. */ + actionId: {} | undefined; } interface CodeActionAll { changes: FileTextChanges[]; @@ -5273,6 +5273,9 @@ declare namespace ts.server.protocol { command: CommandTypes.GetCombinedCodeFix; arguments: GetCombinedCodeFixRequestArgs; } + interface GetCombinedCodeFixResponse extends Response { + body: CodeActionAll; + } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -5307,7 +5310,7 @@ declare namespace ts.server.protocol { errorCodes?: number[]; } interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { - groupId: {}; + actionId: {}; } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ @@ -6063,11 +6066,11 @@ declare namespace ts.server.protocol { } interface CodeActionAll { changes: FileCodeEdits[]; - commands: {}[] | undefined; + commands?: {}[]; } interface CodeFix extends CodeAction { - /** If present, one may call 'getAllCodeFixesInGroup' with this groupId. */ - groupId: {} | undefined; + /** If present, one may call 'getAllCodeFixesInGroup' with this actionId. */ + actionId?: {}; } /** * Format and format on key response message. diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4e627422c0f78..25e18e1b5d059 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3981,7 +3981,7 @@ declare namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, groupId: {}, formatOptions: FormatCodeSettings): CodeActionAll; + getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4074,8 +4074,8 @@ declare namespace ts { commands?: CodeActionCommand[]; } interface CodeFix extends CodeAction { - /** If present, one may call 'applyAllCodeFixesInGroup' with this groupId. */ - groupId: {} | undefined; + /** If present, one may call 'getCombinedCodeFix' with this actionId. */ + actionId: {} | undefined; } interface CodeActionAll { changes: FileTextChanges[]; diff --git a/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts index 23c5e0cef649e..ffc5be68609af 100644 --- a/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts @@ -10,7 +10,7 @@ ////} verify.codeFixAll({ - groupId: "addMissingInvocationForDecorator", + actionId: "addMissingInvocationForDecorator", newFileContent: `declare function foo(): (...args: any[]) => void; class C { diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all.ts b/tests/cases/fourslash/codeFixAddMissingMember_all.ts index d21ee65ead4e8..ec19745bdec31 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all.ts @@ -9,7 +9,7 @@ ////} verify.codeFixAll({ - groupId: "addMissingMember", + actionId: "addMissingMember", newFileContent: // TODO: GH#18445 `class C { diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts index 1143780706d43..abe81874a13bb 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts @@ -14,7 +14,7 @@ ////} verify.codeFixAll({ - groupId: "addMissingMember", + actionId: "addMissingMember", newFileContent: // TODO: GH#18445 GH#20073 `class C { diff --git a/tests/cases/fourslash/codeFixCannotFindModule_all.ts b/tests/cases/fourslash/codeFixCannotFindModule_all.ts index 9477228549ef1..1ea9423e5bd31 100644 --- a/tests/cases/fourslash/codeFixCannotFindModule_all.ts +++ b/tests/cases/fourslash/codeFixCannotFindModule_all.ts @@ -21,7 +21,7 @@ test.setTypesRegistry({ goTo.marker(); verify.codeFixAll({ - groupId: "fixCannotFindModule", + actionId: "fixCannotFindModule", commands: [ { packageName: "@types/abs", file: "/a.ts", type: "install package" }, { packageName: "@types/zap", file: "/a.ts", type: "install package" }, diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts index e96905490d7f1..0fd13059933eb 100644 --- a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts @@ -1,7 +1,9 @@ +/// + // @strict: true ////function f(a: ?number, b: string!) {} verify.codeFixAll({ - groupId: "fixJSDocTypes_plain", + actionId: "fixJSDocTypes_plain", newFileContent: "function f(a: number | null, b: string) {}", }) diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts index 7757451ee799e..a7a74909f0e13 100644 --- a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts @@ -1,7 +1,9 @@ +/// + // @strict: true ////function f(a: ?number, b: string!) {} verify.codeFixAll({ - groupId: "fixJSDocTypes_nullable", + actionId: "fixJSDocTypes_nullable", newFileContent: "function f(a: number | null | undefined, b: string) {}", }) diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts index 01443e39eaffe..c68423375fc2c 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts @@ -7,7 +7,7 @@ ////class C extends A {} verify.codeFixAll({ - groupId: "fixClassDoesntImplementInheritedAbstractMember", + actionId: "fixClassDoesntImplementInheritedAbstractMember", // TODO: GH#20073 GH#18445 newFileContent: `abstract class A { diff --git a/tests/cases/fourslash/codeFixClassImplementInterface_all.ts b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts index 74475da749e9c..dcc956cc032bd 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface_all.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts @@ -6,7 +6,7 @@ ////class D implements J {} verify.codeFixAll({ - groupId: "fixClassIncorrectlyImplementsInterface", + actionId: "fixClassIncorrectlyImplementsInterface", // TODO: GH#20073 GH#18445 newFileContent: `interface I { i(): void; } diff --git a/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts index 80f2bffddb333..b18a30686fc8a 100644 --- a/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts +++ b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts @@ -15,7 +15,7 @@ ////} verify.codeFixAll({ - groupId: "classSuperMustPrecedeThisAccess", + actionId: "classSuperMustPrecedeThisAccess", newFileContent: `class C extends Object { constructor() { super();\r diff --git a/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts index 8091a7e1360c9..93b5a444fcb9e 100644 --- a/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts +++ b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - groupId: "constructorForDerivedNeedSuperCall", + actionId: "constructorForDerivedNeedSuperCall", // TODO: GH#18445 GH#20073 newFileContent: `class C extends Object { constructor() {super();\r diff --git a/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts index 2b9a969ad3146..98d5ebb207a3c 100644 --- a/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts +++ b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts @@ -7,7 +7,7 @@ ////const y: Foo.bar = ""; verify.codeFixAll({ - groupId: "correctQualifiedNameToIndexedAccessType", + actionId: "correctQualifiedNameToIndexedAccessType", newFileContent: `interface Foo { bar: string; diff --git a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts index 14d947f61d037..a8a2f4f575425 100644 --- a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts +++ b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts @@ -10,7 +10,7 @@ ////x = true; verify.codeFixAll({ - groupId: "disableJsDiagnostics", + actionId: "disableJsDiagnostics", newFileContent: `let x = ""; // @ts-ignore\r diff --git a/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts index c04458d6f2273..2f3c0cc74134f 100644 --- a/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts +++ b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts @@ -5,7 +5,7 @@ ////class D extends I {} verify.codeFixAll({ - groupId: "extendsInterfaceBecomesImplements", + actionId: "extendsInterfaceBecomesImplements", newFileContent: `interface I {} class C implements I {} class D implements I {}`, diff --git a/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts index 646621487f2b0..e7e94bac3461a 100644 --- a/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts +++ b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts @@ -9,7 +9,7 @@ ////} verify.codeFixAll({ - groupId: "forgottenThisPropertyAccess", + actionId: "forgottenThisPropertyAccess", newFileContent: `class C { foo: number; diff --git a/tests/cases/fourslash/codeFixInferFromUsage_all.ts b/tests/cases/fourslash/codeFixInferFromUsage_all.ts index e5502363d69dd..836fc334d8c52 100644 --- a/tests/cases/fourslash/codeFixInferFromUsage_all.ts +++ b/tests/cases/fourslash/codeFixInferFromUsage_all.ts @@ -12,7 +12,7 @@ ////} verify.codeFixAll({ - groupId: "inferFromUsage", + actionId: "inferFromUsage", newFileContent: `function f(x: number, y: string) { x += 0; diff --git a/tests/cases/fourslash/codeFixSpelling_all.ts b/tests/cases/fourslash/codeFixSpelling_all.ts index 0fc7e50b95bd1..29232f7833f02 100644 --- a/tests/cases/fourslash/codeFixSpelling_all.ts +++ b/tests/cases/fourslash/codeFixSpelling_all.ts @@ -6,7 +6,7 @@ ////} verify.codeFixAll({ - groupId: "fixSpelling", + actionId: "fixSpelling", newFileContent: `function f(s: string) { s.toString(); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts index 3a6f94e2c12f3..817f7c65da857 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - groupId: "unusedIdentifier_delete", + actionId: "unusedIdentifier_delete", newFileContent: `function f() { }`, diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts index 8f67ff5d23415..8030490605f9c 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - groupId: "unusedIdentifier_prefix", + actionId: "unusedIdentifier_prefix", newFileContent: `function f(_a, _b) { const x = 0; // Can't be prefixed, ignored diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 615101ae4acb6..d7f84c1d79115 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -283,7 +283,7 @@ declare namespace FourSlashInterface { docCommentTemplateAt(markerName: string | FourSlashInterface.Marker, expectedOffset: number, expectedText: string): void; noDocCommentTemplateAt(markerName: string | FourSlashInterface.Marker): void; rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void; - codeFixAll(options: { groupId: string, newFileContent: string, commands?: {}[] }): void; + codeFixAll(options: { actionId: string, newFileContent: string, commands?: {}[] }): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: FormatCodeOptions): void; rangeIs(expectedText: string, includeWhiteSpace?: boolean): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void; From 8a1865f026f44dc822235bb84760a03dfc2b2a2f Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 30 Nov 2017 15:47:41 -0800 Subject: [PATCH 03/11] Code review --- src/server/protocol.ts | 4 ++-- src/server/session.ts | 2 +- src/services/codeFixProvider.ts | 4 ++-- src/services/codefixes/fixAddMissingMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 4 ++-- .../codefixes/fixClassSuperMustPrecedeThisAccess.ts | 4 ++-- src/services/codefixes/inferFromUsage.ts | 6 +++--- src/services/types.ts | 2 +- src/services/utilities.ts | 12 ++---------- 9 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 84e154a9a1703..af8b34e12d2b2 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -542,7 +542,7 @@ namespace ts.server.protocol { } export interface GetCombinedCodeFixResponse extends Response { - body: CodeActionAll; + body: CombinedCodeActions; } export interface ApplyCodeActionCommandRequest extends Request { @@ -1596,7 +1596,7 @@ namespace ts.server.protocol { commands?: {}[]; } - export interface CodeActionAll { + export interface CombinedCodeActions { changes: FileCodeEdits[]; commands?: {}[]; } diff --git a/src/server/session.ts b/src/server/session.ts index 2699fff62c5a9..cbe447e3c3b0f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1567,7 +1567,7 @@ namespace ts.server { } } - private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeActionAll | CodeActionAll { + private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CodeActionAll { const { file, project } = this.getFileAndProject(args); const formatOptions = this.projectService.getFormatCodeOptions(file); const res = project.getLanguageService().getCombinedCodeFix(file, args.actionId, formatOptions); diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 99293fd257e1c..a5a9ab7730fd2 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -3,8 +3,8 @@ namespace ts { export interface CodeFixRegistration { errorCodes: number[]; getCodeActions(context: CodeFixContext): CodeFix[] | undefined; - actionIds: string[]; - getAllCodeActions(context: CodeFixAllContext): CodeActionAll; + actionIds?: string[]; + getAllCodeActions?(context: CodeFixAllContext): CodeActionAll; } export interface CodeFixContextBase extends textChanges.TextChangesContext { diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 3c05750c77455..e59b6136929fd 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -25,7 +25,7 @@ namespace ts.codefix { const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); if (!info) return; const { classDeclaration, classDeclarationSourceFile, classOpenBrace, inJs, makeStatic, token, call } = info; - if (!addToSeenStrings(seenNames, token.text)) { + if (!addToSeen(seenNames, token.text)) { return; } diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index c6fd457be4b24..487941dde4381 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -17,10 +17,10 @@ namespace ts.codefix { }, actionIds: [actionId], getAllCodeActions(context) { - const seenClassDeclarations: true[] = []; + const seenClassDeclarations = createMap(); return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file!, diag.start!); - if (addToSeenIds(seenClassDeclarations, getNodeId(classDeclaration))) { + if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) { addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, context.newLineCharacter, changes); } diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 4dd5c72cf520d..c0d79b7b79fb0 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -15,12 +15,12 @@ namespace ts.codefix { actionIds: [actionId], getAllCodeActions(context) { const { newLineCharacter, sourceFile } = context; - const seenClasses: true[] = []; // Ensure we only do this once per class. + const seenClasses = createMap(); // Ensure we only do this once per class. return codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file!, diag.start!); if (!nodes) return; const { constructor, superCall } = nodes; - if (addToSeenIds(seenClasses, getNodeId(constructor.parent))) { + if (addToSeen(seenClasses, getNodeId(constructor.parent))) { doChange(changes, sourceFile, constructor, superCall, newLineCharacter); } }); diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 1c04e1fa9a6f6..d4afafd29baf2 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -41,7 +41,7 @@ namespace ts.codefix { actionIds: [actionId], getAllCodeActions(context) { const { sourceFile, program, cancellationToken } = context; - const seenFunctions: true[] = []; + const seenFunctions = createMap(); return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { const fix = getFix(sourceFile, getTokenAtPosition(err.file!, err.start!, /*includeJsDocComment*/ false), err.code, program, cancellationToken, seenFunctions); if (fix) changes.push(...fix.textChanges); @@ -65,7 +65,7 @@ namespace ts.codefix { readonly textChanges: TextChange[]; } - function getFix(sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: true[]): Fix | undefined { + function getFix(sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: Map): Fix | undefined { if (!isAllowedTokenKind(token.kind)) { return undefined; } @@ -89,7 +89,7 @@ namespace ts.codefix { } // falls through case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: - return !seenFunctions || addToSeenIds(seenFunctions, getNodeId(containingFunction)) + return !seenFunctions || addToSeen(seenFunctions, getNodeId(containingFunction)) ? getCodeActionForParameters(token.parent, containingFunction, sourceFile, program, cancellationToken) : undefined; diff --git a/src/services/types.ts b/src/services/types.ts index f4a15e100a4f8..121efc8b388e1 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -412,7 +412,7 @@ namespace ts { export interface CodeFix extends CodeAction { /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId: {} | undefined; + actionId?: {} | undefined; } export interface CodeActionAll { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 44a5f0686fe23..1a0460e8841c6 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1101,16 +1101,8 @@ namespace ts { } /** Add a value to a set, and return true if it wasn't already present. */ - export function addToSeenIds(seen: true[], key: number): boolean { - if (seen[key]) { - return false; - } - seen[key] = true; - return true; - } - - /** Add a value to a set, and return true if it wasn't already present. */ - export function addToSeenStrings(seen: Map, key: string): boolean { + export function addToSeen(seen: Map, key: string | number): boolean { + key = String(key); if (seen.has(key)) { return false; } From a8b3afc752eae5b8609ec26ba7d91c2e886f29ef Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 1 Dec 2017 07:52:08 -0800 Subject: [PATCH 04/11] Rename things --- src/services/codeFixProvider.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index a5a9ab7730fd2..6c488a1c72a36 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -24,32 +24,32 @@ namespace ts { } export namespace codefix { - const codeFixes: CodeFixRegistration[][] = []; - const groups = createMap(); + const codeFixRegistrations: CodeFixRegistration[][] = []; + const actionIdToRegistration = createMap(); - export function registerCodeFix(codeFix: CodeFixRegistration) { - for (const error of codeFix.errorCodes) { - let fixes = codeFixes[error]; - if (!fixes) { - fixes = []; - codeFixes[error] = fixes; + export function registerCodeFix(reg: CodeFixRegistration) { + for (const error of reg.errorCodes) { + let registrations = codeFixRegistrations[error]; + if (!registrations) { + registrations = []; + codeFixRegistrations[error] = registrations; } - fixes.push(codeFix); + registrations.push(reg); } - if (codeFix.actionIds) { - for (const gid of codeFix.actionIds) { - Debug.assert(!groups.has(gid)); - groups.set(gid, codeFix); + if (reg.actionIds) { + for (const actionId of reg.actionIds) { + Debug.assert(!actionIdToRegistration.has(actionId)); + actionIdToRegistration.set(actionId, reg); } } } export function getSupportedErrorCodes() { - return Object.keys(codeFixes); + return Object.keys(codeFixRegistrations); } export function getFixes(context: CodeFixContext): CodeFix[] { - const fixes = codeFixes[context.errorCode]; + const fixes = codeFixRegistrations[context.errorCode]; const allActions: CodeFix[] = []; forEach(fixes, f => { @@ -71,7 +71,7 @@ namespace ts { export function getAllFixes(context: CodeFixAllContext): CodeActionAll { // Currently actionId is always a string. - return groups.get(cast(context.actionId, isString)).getAllCodeActions!(context); + return actionIdToRegistration.get(cast(context.actionId, isString))!.getAllCodeActions!(context); } function createCodeActionAll(changes: FileTextChanges[], commands?: CodeActionCommand[]): CodeActionAll { From a38216c3c4d5cc1d1f5dff1ffef1edd9bd7b2518 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 1 Dec 2017 08:26:23 -0800 Subject: [PATCH 05/11] Update API baselines --- src/services/types.ts | 2 +- tests/baselines/reference/api/tsserverlibrary.d.ts | 8 ++++---- tests/baselines/reference/api/typescript.d.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/types.ts b/src/services/types.ts index 121efc8b388e1..92a1056152210 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -412,7 +412,7 @@ namespace ts { export interface CodeFix extends CodeAction { /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId?: {} | undefined; + actionId?: {}; } export interface CodeActionAll { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 251fd337e75df..6770534ad2da3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4075,8 +4075,8 @@ declare namespace ts { commands?: CodeActionCommand[]; } interface CodeFix extends CodeAction { - /** If present, one may call 'applyAllCodeFixesInGroup' with this actionId. */ - actionId: {} | undefined; + /** If present, one may call 'getCombinedCodeFix' with this actionId. */ + actionId?: {}; } interface CodeActionAll { changes: FileTextChanges[]; @@ -5275,7 +5275,7 @@ declare namespace ts.server.protocol { arguments: GetCombinedCodeFixRequestArgs; } interface GetCombinedCodeFixResponse extends Response { - body: CodeActionAll; + body: CombinedCodeActions; } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; @@ -6065,7 +6065,7 @@ declare namespace ts.server.protocol { /** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */ commands?: {}[]; } - interface CodeActionAll { + interface CombinedCodeActions { changes: FileCodeEdits[]; commands?: {}[]; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e262a0b299411..25d5cc574e2c1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4076,7 +4076,7 @@ declare namespace ts { } interface CodeFix extends CodeAction { /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId: {} | undefined; + actionId?: {}; } interface CodeActionAll { changes: FileTextChanges[]; From c621e3f8be136792f40ac0090b6e2d5e1d1a2978 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 1 Dec 2017 14:46:22 -0800 Subject: [PATCH 06/11] CodeActionAll -> CombinedCodeActions --- src/server/session.ts | 2 +- src/services/codeFixProvider.ts | 14 +++++++------- src/services/services.ts | 2 +- src/services/types.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index cbe447e3c3b0f..f484a57552ef7 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1567,7 +1567,7 @@ namespace ts.server { } } - private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CodeActionAll { + private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { const { file, project } = this.getFileAndProject(args); const formatOptions = this.projectService.getFormatCodeOptions(file); const res = project.getLanguageService().getCombinedCodeFix(file, args.actionId, formatOptions); diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 6c488a1c72a36..10080e17f8879 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -4,7 +4,7 @@ namespace ts { errorCodes: number[]; getCodeActions(context: CodeFixContext): CodeFix[] | undefined; actionIds?: string[]; - getAllCodeActions?(context: CodeFixAllContext): CodeActionAll; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } export interface CodeFixContextBase extends textChanges.TextChangesContext { @@ -69,12 +69,12 @@ namespace ts { return allActions; } - export function getAllFixes(context: CodeFixAllContext): CodeActionAll { + export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { // Currently actionId is always a string. return actionIdToRegistration.get(cast(context.actionId, isString))!.getAllCodeActions!(context); } - function createCodeActionAll(changes: FileTextChanges[], commands?: CodeActionCommand[]): CodeActionAll { + function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { return { changes, commands }; } @@ -82,18 +82,18 @@ namespace ts { return { fileName, textChanges }; } - export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CodeActionAll { + export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CombinedCodeActions { const commands: CodeActionCommand[] = []; const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); - return createCodeActionAll(changes, commands.length === 0 ? undefined : commands); + return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); } - export function codeFixAllWithTextChanges(context: CodeFixAllContext, errorCodes: number[], use: (changes: Push, error: Diagnostic) => void): CodeActionAll { + export function codeFixAllWithTextChanges(context: CodeFixAllContext, errorCodes: number[], use: (changes: Push, error: Diagnostic) => void): CombinedCodeActions { const changes: TextChange[] = []; eachDiagnostic(context, errorCodes, diag => use(changes, diag)); changes.sort((a, b) => b.span.start - a.span.start); - return createCodeActionAll([createFileTextChanges(context.sourceFile.fileName, changes)]); + return createCombinedCodeActions([createFileTextChanges(context.sourceFile.fileName, changes)]); } function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: Diagnostic) => void): void { diff --git a/src/services/services.ts b/src/services/services.ts index f07b8c14c5a5c..d50cfbc307555 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1894,7 +1894,7 @@ namespace ts { }); } - function getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll { + function getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const newLineCharacter = getNewLineOrDefaultFromHost(host); diff --git a/src/services/types.ts b/src/services/types.ts index 92a1056152210..832075ac98b59 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,7 +295,7 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CodeActionAll; + getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -415,7 +415,7 @@ namespace ts { actionId?: {}; } - export interface CodeActionAll { + export interface CombinedCodeActions { changes: FileTextChanges[]; commands: CodeActionCommand[] | undefined; } From 2db8649b38cc6572ce6d94430c17684c3d1fbbe8 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 5 Dec 2017 10:21:05 -0800 Subject: [PATCH 07/11] Take a `scope` parameter instead of `fileName` for flexibility --- src/harness/fourslash.ts | 2 +- src/server/protocol.ts | 8 +++++++- src/server/session.ts | 7 ++++--- src/services/services.ts | 5 +++-- src/services/types.ts | 4 +++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0e4b656554218..df7bb6aab0bb3 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2427,7 +2427,7 @@ Actual: ${stringify(fullActual)}`); const { actionId, newFileContent } = options; const actionIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.actionId); ts.Debug.assert(ts.contains(actionIds, actionId), "No available code fix has that group id.", () => `Expected '${actionId}'. Available action ids: ${actionIds}`); - const { changes, commands } = this.languageService.getCombinedCodeFix(this.activeFile.fileName, actionId, this.formatCodeSettings); + const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, actionId, this.formatCodeSettings); assert.deepEqual(commands, options.commands); this.applyChanges(changes); this.verifyCurrentFileContent(newFileContent); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 68ad91bb16da5..231bf5599554a 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -597,10 +597,16 @@ namespace ts.server.protocol { errorCodes?: number[]; } - export interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { + export interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; actionId: {}; } + export interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; + } + export interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; diff --git a/src/server/session.ts b/src/server/session.ts index 5445e76289d7c..a62dca822284e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1567,10 +1567,11 @@ namespace ts.server { } } - private getCombinedCodeFix(args: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { - const { file, project } = this.getFileAndProject(args); + private getCombinedCodeFix({ scope, actionId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { + Debug.assert(scope.type === "file"); + const { file, project } = this.getFileAndProject(scope.args); const formatOptions = this.projectService.getFormatCodeOptions(file); - const res = project.getLanguageService().getCombinedCodeFix(file, args.actionId, formatOptions); + const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, actionId, formatOptions); if (simplifiedResult) { return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; } diff --git a/src/services/services.ts b/src/services/services.ts index 24c6b13e8bb0b..83b5b0259b6d4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1895,9 +1895,10 @@ namespace ts { }); } - function getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { + function getCombinedCodeFix(scope: CombinedCodeFixScope, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); + Debug.assert(scope.type === "file"); + const sourceFile = getValidSourceFile(scope.fileName); const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); diff --git a/src/services/types.ts b/src/services/types.ts index 4fbfd28e5f0c5..32ed0bec59970 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,7 +295,7 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCombinedCodeFix(scope: CombinedCodeFixScope, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -323,6 +323,8 @@ namespace ts { dispose(): void; } + export interface CombinedCodeFixScope { type: "file", fileName: string }; + export interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } From f2031beee9db369538e5b6b1be083fc0753c71c0 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 5 Dec 2017 11:59:30 -0800 Subject: [PATCH 08/11] Renames and bugfixes --- src/harness/fourslash.ts | 12 +- src/harness/harness.ts | 4 +- src/server/client.ts | 4 +- src/server/protocol.ts | 10 +- src/server/session.ts | 4 +- src/services/codeFixProvider.ts | 24 +-- .../addMissingInvocationForDecorator.ts | 6 +- ...correctQualifiedNameToIndexedAccessType.ts | 6 +- .../codefixes/disableJsDiagnostics.ts | 10 +- src/services/codefixes/fixAddMissingMember.ts | 24 +-- src/services/codefixes/fixCannotFindModule.ts | 6 +- ...sDoesntImplementInheritedAbstractMember.ts | 6 +- .../fixClassIncorrectlyImplementsInterface.ts | 10 +- .../fixClassSuperMustPrecedeThisAccess.ts | 6 +- .../fixConstructorForDerivedNeedSuperCall.ts | 6 +- .../fixExtendsInterfaceBecomesImplements.ts | 6 +- .../fixForgottenThisPropertyAccess.ts | 6 +- src/services/codefixes/fixJSDocTypes.ts | 18 +-- src/services/codefixes/fixSpelling.ts | 6 +- src/services/codefixes/fixUnusedIdentifier.ts | 20 +-- src/services/codefixes/helpers.ts | 40 +++-- src/services/codefixes/importFixes.ts | 6 +- src/services/codefixes/inferFromUsage.ts | 6 +- src/services/services.ts | 6 +- src/services/types.ts | 12 +- .../reference/api/tsserverlibrary.d.ts | 33 +++-- tests/baselines/reference/api/typescript.d.ts | 14 +- ...FixAddMissingInvocationForDecorator_all.ts | 2 +- .../fourslash/codeFixAddMissingMember_all.ts | 2 +- .../codeFixAddMissingMember_all_js.ts | 2 +- .../fourslash/codeFixCannotFindModule_all.ts | 2 +- .../fourslash/codeFixChangeJSDocSyntax_all.ts | 2 +- .../codeFixChangeJSDocSyntax_all_nullable.ts | 2 +- ...ClassImplementClassFunctionVoidInferred.ts | 16 +- ...prExtendsAbstractExpressionWithTypeArgs.ts | 15 +- ...assExtendAbstractExpressionWithTypeArgs.ts | 13 +- .../codeFixClassExtendAbstractGetterSetter.ts | 38 +++-- .../codeFixClassExtendAbstractMethod.ts | 34 +++-- .../codeFixClassExtendAbstractMethodThis.ts | 16 +- ...stractMethodTypeParamsInstantiateNumber.ts | 16 +- ...endAbstractMethodTypeParamsInstantiateU.ts | 16 +- .../codeFixClassExtendAbstractMethod_all.ts | 18 ++- .../codeFixClassExtendAbstractProperty.ts | 18 ++- .../codeFixClassExtendAbstractPropertyThis.ts | 21 ++- ...FixClassExtendAbstractProtectedProperty.ts | 21 ++- ...odeFixClassExtendAbstractPublicProperty.ts | 20 ++- ...ImplementClassAbstractGettersAndSetters.ts | 48 ++++-- ...ClassImplementClassFunctionVoidInferred.ts | 25 +++- ...xClassImplementClassMultipleSignatures1.ts | 29 ++-- ...xClassImplementClassMultipleSignatures2.ts | 35 +++-- ...FixClassImplementClassPropertyModifiers.ts | 36 +++-- ...FixClassImplementClassPropertyTypeQuery.ts | 20 ++- .../codeFixClassImplementDeepInheritance.ts | 98 +++++++----- .../codeFixClassImplementDefaultClass.ts | 17 ++- ...odeFixClassImplementInterfaceArrayTuple.ts | 33 +++-- ...xClassImplementInterfaceClassExpression.ts | 17 ++- .../codeFixClassImplementInterfaceComments.ts | 63 +++++--- ...lementInterfaceComputedPropertyLiterals.ts | 42 ++++-- ...aceComputedPropertyNameWellKnownSymbols.ts | 98 +++++++----- ...deFixClassImplementInterfaceInNamespace.ts | 39 +++-- ...ssImplementInterfaceIndexSignaturesBoth.ts | 24 ++- ...ImplementInterfaceIndexSignaturesNumber.ts | 20 ++- ...ImplementInterfaceIndexSignaturesString.ts | 23 ++- ...codeFixClassImplementInterfaceIndexType.ts | 22 ++- ...mplementInterfaceInheritsAbstractMethod.ts | 29 ++-- ...odeFixClassImplementInterfaceMappedType.ts | 22 ++- ...ImplementInterfaceMemberNestedTypeAlias.ts | 33 +++-- ...ixClassImplementInterfaceMemberOrdering.ts | 139 +++++++++++------- ...xClassImplementInterfaceMemberTypeAlias.ts | 25 ++-- ...mentInterfaceMethodThisAndSelfReference.ts | 25 +++- ...ssImplementInterfaceMethodTypePredicate.ts | 34 +++-- ...tInterfaceMultipleMembersAndPunctuation.ts | 58 +++++--- ...assImplementInterfaceMultipleSignatures.ts | 33 +++-- ...plementInterfaceMultipleSignaturesRest1.ts | 33 +++-- ...plementInterfaceMultipleSignaturesRest2.ts | 33 +++-- ...lassImplementInterfaceNamespaceConflict.ts | 33 +++-- ...ClassImplementInterfaceOptionalProperty.ts | 24 ++- .../codeFixClassImplementInterfaceProperty.ts | 34 +++-- ...assImplementInterfacePropertySignatures.ts | 78 ++++++---- ...FixClassImplementInterfaceQualifiedName.ts | 24 +-- ...mentInterfaceTypeParamInstantiateDeeply.ts | 21 ++- ...mentInterfaceTypeParamInstantiateNumber.ts | 19 ++- ...ImplementInterfaceTypeParamInstantiateT.ts | 19 ++- ...ImplementInterfaceTypeParamInstantiateU.ts | 19 ++- ...xClassImplementInterfaceTypeParamMethod.ts | 24 ++- .../codeFixClassImplementInterface_all.ts | 22 +-- ...eFixClassSuperMustPrecedeThisAccess_all.ts | 2 +- ...xConstructorForDerivedNeedSuperCall_all.ts | 2 +- ...ectQualifiedNameToIndexedAccessType_all.ts | 2 +- .../codeFixDisableJsDiagnosticsInFile_all.ts | 2 +- ...ixExtendsInterfaceBecomesImplements_all.ts | 2 +- .../codeFixForgottenThisPropertyAccess_all.ts | 2 +- .../fourslash/codeFixInferFromUsage_all.ts | 2 +- tests/cases/fourslash/codeFixSpelling_all.ts | 2 +- .../codeFixUnusedIdentifier_all_delete.ts | 2 +- .../codeFixUnusedIdentifier_all_prefix.ts | 2 +- tests/cases/fourslash/fourslash.ts | 2 +- 97 files changed, 1317 insertions(+), 760 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index df7bb6aab0bb3..3c73ac0d759e4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2424,10 +2424,10 @@ Actual: ${stringify(fullActual)}`); } public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void { - const { actionId, newFileContent } = options; - const actionIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.actionId); - ts.Debug.assert(ts.contains(actionIds, actionId), "No available code fix has that group id.", () => `Expected '${actionId}'. Available action ids: ${actionIds}`); - const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, actionId, this.formatCodeSettings); + const { fixId, newFileContent } = options; + const fixIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId); + ts.Debug.assert(ts.contains(fixIds, fixId), "No available code fix has that group id.", () => `Expected '${fixId}'. Available action ids: ${fixIds}`); + const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings); assert.deepEqual(commands, options.commands); this.applyChanges(changes); this.verifyCurrentFileContent(newFileContent); @@ -2494,7 +2494,7 @@ Actual: ${stringify(fullActual)}`); * Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found. * @param fileName Path to file where error should be retrieved from. */ - private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFix[] { + private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] { const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({ start: diagnostic.start, length: diagnostic.length, @@ -4595,7 +4595,7 @@ namespace FourSlashInterface { } export interface VerifyCodeFixAllOptions { - actionId: string; + fixId: string; newFileContent: string; commands: ReadonlyArray<{}>; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 0b361f8db389b..8f56a23994352 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -36,8 +36,8 @@ namespace assert { export function isFalse(expr: boolean, msg = "Expected value to be false."): void { assert(!expr, msg); } - export function equal(a: T, b: T, msg = "Expected values to be equal."): void { - assert(a === b, msg); + export function equal(a: T, b: T, msg?: string): void { + assert(a === b, msg || (() => `Expected to be equal:\nExpected:\n${JSON.stringify(a)}\nActual:\n${JSON.stringify(b)}`)); } export function notEqual(a: T, b: T, msg = "Expected values to not be equal."): void { assert(a !== b, msg); diff --git a/src/server/client.ts b/src/server/client.ts index 63bee60872b19..f73fdd0f44c3c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -553,13 +553,13 @@ namespace ts.server { return notImplemented(); } - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeFix[] { + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeFixAction[] { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - return response.body.map(({ description, changes, actionId }) => ({ description, changes: this.convertChanges(changes, file), actionId })); + return response.body.map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 231bf5599554a..3ada81e07a8cf 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -599,7 +599,7 @@ namespace ts.server.protocol { export interface GetCombinedCodeFixRequestArgs { scope: GetCombinedCodeFixScope; - actionId: {}; + fixId: {}; } export interface GetCombinedCodeFixScope { @@ -1590,7 +1590,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeFix[]; + body?: CodeFixAction[]; } export interface CodeAction { @@ -1607,9 +1607,9 @@ namespace ts.server.protocol { commands?: {}[]; } - export interface CodeFix extends CodeAction { - /** If present, one may call 'getAllCodeFixesInGroup' with this actionId. */ - actionId?: {}; + export interface CodeFixAction extends CodeAction { + /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + fixId?: {}; } /** diff --git a/src/server/session.ts b/src/server/session.ts index a62dca822284e..6ded113f046ab 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1567,11 +1567,11 @@ namespace ts.server { } } - private getCombinedCodeFix({ scope, actionId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { + private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { Debug.assert(scope.type === "file"); const { file, project } = this.getFileAndProject(scope.args); const formatOptions = this.projectService.getFormatCodeOptions(file); - const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, actionId, formatOptions); + const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, formatOptions); if (simplifiedResult) { return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; } diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 10080e17f8879..aa1f2fb6885e7 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -2,8 +2,8 @@ namespace ts { export interface CodeFixRegistration { errorCodes: number[]; - getCodeActions(context: CodeFixContext): CodeFix[] | undefined; - actionIds?: string[]; + getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; + fixIds?: string[]; getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } @@ -15,7 +15,7 @@ namespace ts { } export interface CodeFixAllContext extends CodeFixContextBase { - actionId: {}; + fixId: {}; } export interface CodeFixContext extends CodeFixContextBase { @@ -25,7 +25,7 @@ namespace ts { export namespace codefix { const codeFixRegistrations: CodeFixRegistration[][] = []; - const actionIdToRegistration = createMap(); + const fixIdToRegistration = createMap(); export function registerCodeFix(reg: CodeFixRegistration) { for (const error of reg.errorCodes) { @@ -36,10 +36,10 @@ namespace ts { } registrations.push(reg); } - if (reg.actionIds) { - for (const actionId of reg.actionIds) { - Debug.assert(!actionIdToRegistration.has(actionId)); - actionIdToRegistration.set(actionId, reg); + if (reg.fixIds) { + for (const fixId of reg.fixIds) { + Debug.assert(!fixIdToRegistration.has(fixId)); + fixIdToRegistration.set(fixId, reg); } } } @@ -48,9 +48,9 @@ namespace ts { return Object.keys(codeFixRegistrations); } - export function getFixes(context: CodeFixContext): CodeFix[] { + export function getFixes(context: CodeFixContext): CodeFixAction[] { const fixes = codeFixRegistrations[context.errorCode]; - const allActions: CodeFix[] = []; + const allActions: CodeFixAction[] = []; forEach(fixes, f => { const actions = f.getCodeActions(context); @@ -70,8 +70,8 @@ namespace ts { } export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { - // Currently actionId is always a string. - return actionIdToRegistration.get(cast(context.actionId, isString))!.getAllCodeActions!(context); + // Currently fixId is always a string. + return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context); } function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index 17549e73d5631..d063df87be422 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -1,14 +1,14 @@ /* @internal */ namespace ts.codefix { - const actionId = "addMissingInvocationForDecorator"; + const fixId = "addMissingInvocationForDecorator"; const errorCodes = [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code]; registerCodeFix({ errorCodes, getCodeActions: (context) => { const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, actionId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)), }); diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index 352176f111d84..3d99b5712485f 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "correctQualifiedNameToIndexedAccessType"; + const fixId = "correctQualifiedNameToIndexedAccessType"; const errorCodes = [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code]; registerCodeFix({ errorCodes, @@ -9,9 +9,9 @@ namespace ts.codefix { if (!qualifiedName) return undefined; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); - return [{ description, changes, actionId }]; + return [{ description, changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: (context) => codeFixAll(context, errorCodes, (changes, diag) => { const q = getQualifiedName(diag.file, diag.start); if (q) { diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 61d11515b8f9d..6a59e61d52dec 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "disableJsDiagnostics"; + const fixId = "disableJsDiagnostics"; const errorCodes = mapDefined(Object.keys(Diagnostics), key => { const diag = (Diagnostics as MapLike)[key]; return diag.category === DiagnosticCategory.Error ? diag.code : undefined; @@ -18,7 +18,7 @@ namespace ts.codefix { return [{ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], - actionId, + fixId, }, { description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), @@ -29,11 +29,11 @@ namespace ts.codefix { }, newText: `// @ts-nocheck${newLineCharacter}` }])], - // actionId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. - actionId: undefined, + // fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. + fixId: undefined, }]; }, - actionIds: [actionId], // No point applying as a group, doing it once will fix all errors + fixIds: [fixId], // No point applying as a group, doing it once will fix all errors getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { if (err.start !== undefined) { changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index ec3c19156f087..e20bdf0fb5b86 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -4,7 +4,7 @@ namespace ts.codefix { Diagnostics.Property_0_does_not_exist_on_type_1.code, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, ]; - const actionId = "addMissingMember"; + const fixId = "addMissingMember"; registerCodeFix({ errorCodes, getCodeActions(context) { @@ -17,7 +17,7 @@ namespace ts.codefix { getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic); return concatenate(singleElementArray(methodCodeAction), addMember); }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => { const seenNames = createMap(); return codeFixAll(context, errorCodes, (changes, diag) => { @@ -95,11 +95,11 @@ namespace ts.codefix { } } - function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFix | undefined { + function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic, context.newLineCharacter)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); - return { description, changes, actionId }; + return { description, changes, fixId }; } function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean, newLineCharacter: string): void { @@ -125,7 +125,7 @@ namespace ts.codefix { return createStatement(createAssignment(createPropertyAccess(obj, propertyName), createIdentifier("undefined"))); } - function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, makeStatic: boolean): CodeFix[] | undefined { + function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, makeStatic: boolean): CodeFixAction[] | undefined { const typeNode = getTypeNode(context.program.getTypeChecker(), classDeclaration, token); const addProp = createAddPropertyDeclarationAction(context, classDeclarationSourceFile, classDeclaration, makeStatic, token.text, typeNode); return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, classDeclarationSourceFile, classDeclaration, token.text, typeNode)]; @@ -142,10 +142,10 @@ namespace ts.codefix { return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); } - function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFix { + function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic, context.newLineCharacter)); - return { description, changes, actionId }; + return { description, changes, fixId }; } function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode, makeStatic: boolean, newLineCharacter: string): void { @@ -159,7 +159,7 @@ namespace ts.codefix { changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, newLineCharacter); } - function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFix { + function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { // Index signatures cannot have the static modifier. const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( @@ -177,14 +177,14 @@ namespace ts.codefix { typeNode); const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter)); - // No actionId here because code-fix-all currently only works on adding individual named properties. - return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, actionId: undefined }; + // No fixId here because code-fix-all currently only works on adding individual named properties. + return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined }; } - function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFix | undefined { + function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, context.newLineCharacter, makeStatic, inJs)); - return { description, changes, actionId }; + return { description, changes, fixId }; } function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, newLineCharacter: string, makeStatic: boolean, inJs: boolean) { diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 6391849ed165e..2a3736531b2e9 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,13 +1,13 @@ /* @internal */ namespace ts.codefix { - const actionId = "fixCannotFindModule"; + const fixId = "fixCannotFindModule"; const errorCodes = [Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code]; registerCodeFix({ errorCodes, getCodeActions: context => [ - { actionId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) } + { fixId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) } ], - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => { const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start)); if (pkg) { diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 25c3dfd3f4ea9..754735a9d6353 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -4,16 +4,16 @@ namespace ts.codefix { Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code, ]; - const actionId = "fixClassDoesntImplementInheritedAbstractMember"; + const fixId = "fixClassDoesntImplementInheritedAbstractMember"; registerCodeFix({ errorCodes, getCodeActions(context) { const { program, sourceFile, span } = context; const changes = textChanges.ChangeTracker.with(context, t => addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), context.newLineCharacter, t)); - return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, actionId }]; + return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), context.newLineCharacter, changes); }), diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 46d6ce8a20a9b..7076f7da34439 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -1,21 +1,21 @@ /* @internal */ namespace ts.codefix { const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code]; - const actionId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? + const fixId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? registerCodeFix({ errorCodes, getCodeActions(context) { const { newLineCharacter, program, sourceFile, span } = context; const classDeclaration = getClass(sourceFile, span.start); const checker = program.getTypeChecker(); - return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { + return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, newLineCharacter, t)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); - return { description, changes, actionId }; + return { description, changes, fixId }; }); }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions(context) { const seenClassDeclarations = createMap(); return codeFixAll(context, errorCodes, (changes, diag) => { @@ -63,7 +63,7 @@ namespace ts.codefix { function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); if (indexInfoOfKind) { - changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration), newLineCharacter) + changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration), newLineCharacter); } } } diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 14673039c94f9..9b67c3469cf0b 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "classSuperMustPrecedeThisAccess"; + const fixId = "classSuperMustPrecedeThisAccess"; const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code]; registerCodeFix({ errorCodes, @@ -10,9 +10,9 @@ namespace ts.codefix { if (!nodes) return undefined; const { constructor, superCall } = nodes; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall, context.newLineCharacter)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, actionId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions(context) { const { newLineCharacter, sourceFile } = context; const seenClasses = createMap(); // Ensure we only do this once per class. diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 631c85db1fbe7..4c5b24fb0ec8a 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "constructorForDerivedNeedSuperCall"; + const fixId = "constructorForDerivedNeedSuperCall"; const errorCodes = [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code]; registerCodeFix({ errorCodes, @@ -8,9 +8,9 @@ namespace ts.codefix { const { sourceFile } = context; const ctr = getNode(sourceFile, context.span.start); const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr, context.newLineCharacter)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, actionId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, context.sourceFile, getNode(diag.file, diag.start!), context.newLineCharacter)), }); diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index a42d18ec20cda..446b0bddfab98 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "extendsInterfaceBecomesImplements"; + const fixId = "extendsInterfaceBecomesImplements"; const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code]; registerCodeFix({ errorCodes, @@ -10,9 +10,9 @@ namespace ts.codefix { if (!nodes) return undefined; const { extendsToken, heritageClauses } = nodes; const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, actionId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start!); if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses); diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 15d0c061f835b..19610da0b1509 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "forgottenThisPropertyAccess"; + const fixId = "forgottenThisPropertyAccess"; const errorCodes = [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code]; registerCodeFix({ errorCodes, @@ -8,9 +8,9 @@ namespace ts.codefix { const { sourceFile } = context; const token = getNode(sourceFile, context.span.start); const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); - return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, actionId }]; + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { doChange(changes, context.sourceFile, getNode(diag.file, diag.start!)); }), diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts index 68c0614497e92..8c43ed0cc7fb3 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { - const actionIdPlain = "fixJSDocTypes_plain"; - const actionIdNullable = "fixJSDocTypes_nullable"; + const fixIdPlain = "fixJSDocTypes_plain"; + const fixIdNullable = "fixJSDocTypes_nullable"; const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code]; registerCodeFix({ errorCodes, @@ -12,32 +12,32 @@ namespace ts.codefix { if (!info) return undefined; const { typeNode, type } = info; const original = typeNode.getText(sourceFile); - const actions = [fix(type, actionIdPlain)]; + const actions = [fix(type, fixIdPlain)]; if (typeNode.kind === SyntaxKind.JSDocNullableType) { // for nullable types, suggest the flow-compatible `T | null | undefined` // in addition to the jsdoc/closure-compatible `T | null` - actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), actionIdNullable)); + actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable)); } return actions; - function fix(type: Type, actionId: string): CodeFix { + function fix(type: Type, fixId: string): CodeFixAction { const newText = typeString(type, checker); return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]), changes: [createFileTextChanges(sourceFile.fileName, [createChange(typeNode, sourceFile, newText)])], - actionId, + fixId, }; } }, - actionIds: [actionIdPlain, actionIdNullable], + fixIds: [fixIdPlain, fixIdNullable], getAllCodeActions(context) { - const { actionId, program, sourceFile } = context; + const { fixId, program, sourceFile } = context; const checker = program.getTypeChecker(); return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { const info = getInfo(err.file, err.start!, checker); if (!info) return; const { typeNode, type } = info; - const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && actionId === actionIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; + const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && fixId === fixIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; changes.push(createChange(typeNode, sourceFile, typeString(fixedType, checker))); }); } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 8dc3c418d20b1..64cdc3181fb00 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "fixSpelling"; + const fixId = "fixSpelling"; const errorCodes = [ Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, @@ -14,9 +14,9 @@ namespace ts.codefix { const { node, suggestion } = info; const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); - return [{ description, changes, actionId }]; + return [{ description, changes, fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); if (info) doChange(changes, context.sourceFile, info.node, info.suggestion); diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index cafa9d98d82f6..b7d948b3deb4a 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { - const actionIdPrefix = "unusedIdentifier_prefix"; - const actionIdDelete = "unusedIdentifier_delete"; + const fixIdPrefix = "unusedIdentifier_prefix"; + const fixIdDelete = "unusedIdentifier_delete"; const errorCodes = [ Diagnostics._0_is_declared_but_its_value_is_never_read.code, Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code, @@ -11,37 +11,37 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile } = context; const token = getToken(sourceFile, context.span.start); - const result: CodeFix[] = []; + const result: CodeFixAction[] = []; const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); if (deletion.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); - result.push({ description, changes: deletion, actionId: actionIdDelete }); + result.push({ description, changes: deletion, fixId: fixIdDelete }); } const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); if (prefix.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); - result.push({ description, changes: prefix, actionId: actionIdPrefix }); + result.push({ description, changes: prefix, fixId: fixIdPrefix }); } return result; }, - actionIds: [actionIdPrefix, actionIdDelete], + fixIds: [fixIdPrefix, fixIdDelete], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { const { sourceFile } = context; const token = getToken(diag.file!, diag.start!); - switch (context.actionId) { - case actionIdPrefix: + switch (context.fixId) { + case fixIdPrefix: if (isIdentifier(token) && canPrefix(token)) { tryPrefixDeclaration(changes, diag.code, sourceFile, token); } break; - case actionIdDelete: + case fixIdDelete: tryDeleteDeclaration(changes, sourceFile, token); break; default: - Debug.fail(JSON.stringify(context.actionId)); + Debug.fail(JSON.stringify(context.fixId)); } }), }); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index ed5e46c25dd56..bd2710244e696 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -26,7 +26,7 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; // Clone name to remove leading trivia. - const name = getSynthesizedClone(getNameOfDeclaration(declaration)) as PropertyName; + const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration)) as PropertyName; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); @@ -63,17 +63,18 @@ namespace ts.codefix { if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + outputMethod(signature, modifiers, name, createStubbedMethodBody()); break; } for (const signature of signatures) { - signatureToMethodDeclaration(signature, enclosingDeclaration); + // Need to ensure nodes are fresh each time so they can have different positions. + outputMethod(signature, getSynthesizedDeepClones(modifiers), getSynthesizedDeepClone(name)); } if (declarations.length > signatures.length) { const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + outputMethod(signature, modifiers, name, createStubbedMethodBody()); } else { Debug.assert(declarations.length === signatures.length); @@ -82,19 +83,28 @@ namespace ts.codefix { break; } - function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); - if (!signatureDeclaration) { - return; - } + function outputMethod(signature: Signature, modifiers: NodeArray, name: PropertyName, body?: Block): void { + const method = signatureToMethodDeclaration(checker, signature, enclosingDeclaration, modifiers, name, optional, body); + if (method) out(method); + } + } - signatureDeclaration.decorators = undefined; - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclaration.body = body; - out(signatureDeclaration); + function signatureToMethodDeclaration(checker: TypeChecker, signature: Signature, enclosingDeclaration: ClassLikeDeclaration, modifiers: NodeArray, name: PropertyName, optional: boolean, body: Block | undefined) { + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); + if (!signatureDeclaration) { + return undefined; } + + signatureDeclaration.decorators = undefined; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = body; + return signatureDeclaration; + } + + function getSynthesizedDeepClones(nodes: NodeArray | undefined): NodeArray | undefined { + return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone)); } export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index df892494cb7c6..2f6c9a448e503 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -11,7 +11,7 @@ namespace ts.codefix { ], getCodeActions: getImportCodeActions, // TODO: GH#20315 - actionIds: [], + fixIds: [], getAllCodeActions: notImplemented, }); @@ -19,7 +19,7 @@ namespace ts.codefix { // Map from module Id to an array of import declarations in that module. type ImportDeclarationMap = AnyImportSyntax[][]; - interface ImportCodeAction extends CodeFix { + interface ImportCodeAction extends CodeFixAction { kind: ImportCodeActionKind; moduleSpecifier?: string; } @@ -158,7 +158,7 @@ namespace ts.codefix { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), changes, // TODO: GH#20315 - actionId: undefined, + fixId: undefined, kind, moduleSpecifier }; diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index d4afafd29baf2..9bafae98c1505 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.codefix { - const actionId = "inferFromUsage"; + const fixId = "inferFromUsage"; const errorCodes = [ // Variable declarations Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, @@ -36,9 +36,9 @@ namespace ts.codefix { const { declaration, textChanges } = fix; const name = getNameOfDeclaration(declaration); const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name.getText()]); - return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], actionId }]; + return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], fixId }]; }, - actionIds: [actionId], + fixIds: [fixId], getAllCodeActions(context) { const { sourceFile, program, cancellationToken } = context; const seenFunctions = createMap(); diff --git a/src/services/services.ts b/src/services/services.ts index 83b5b0259b6d4..196f4e421062c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1882,7 +1882,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); @@ -1895,14 +1895,14 @@ namespace ts { }); } - function getCombinedCodeFix(scope: CombinedCodeFixScope, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { + function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { synchronizeHostData(); Debug.assert(scope.type === "file"); const sourceFile = getValidSourceFile(scope.fileName); const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); - return codefix.getAllFixes({ actionId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getAllFixes({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); } function applyCodeActionCommand(action: CodeActionCommand): Promise; diff --git a/src/services/types.ts b/src/services/types.ts index 32ed0bec59970..a9d7ca509faec 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -294,8 +294,8 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(scope: CombinedCodeFixScope, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -323,7 +323,7 @@ namespace ts { dispose(): void; } - export interface CombinedCodeFixScope { type: "file", fileName: string }; + export interface CombinedCodeFixScope { type: "file"; fileName: string; } export interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; @@ -412,9 +412,9 @@ namespace ts { commands?: CodeActionCommand[]; } - export interface CodeFix extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId?: {}; + export interface CodeFixAction extends CodeAction { + /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + fixId?: {}; } export interface CombinedCodeActions { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e2295a15f62a1..8ed70bc6c1005 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3983,8 +3983,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4000,6 +4000,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } @@ -4076,9 +4080,9 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } - interface CodeFix extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId?: {}; + interface CodeFixAction extends CodeAction { + /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + fixId?: {}; } interface CombinedCodeActions { changes: FileTextChanges[]; @@ -5313,8 +5317,13 @@ declare namespace ts.server.protocol { */ errorCodes?: number[]; } - interface GetCombinedCodeFixRequestArgs extends FileRequestArgs { - actionId: {}; + interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; + fixId: {}; + } + interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ @@ -6058,7 +6067,7 @@ declare namespace ts.server.protocol { } interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeFix[]; + body?: CodeFixAction[]; } interface CodeAction { /** Description of the code action to display in the UI of the editor */ @@ -6072,9 +6081,9 @@ declare namespace ts.server.protocol { changes: FileCodeEdits[]; commands?: {}[]; } - interface CodeFix extends CodeAction { - /** If present, one may call 'getAllCodeFixesInGroup' with this actionId. */ - actionId?: {}; + interface CodeFixAction extends CodeAction { + /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + fixId?: {}; } /** * Format and format on key response message. @@ -7100,7 +7109,7 @@ declare namespace ts.server { private getApplicableRefactors(args); private getEditsForRefactor(args, simplifiedResult); private getCodeFixes(args, simplifiedResult); - private getCombinedCodeFix(args, simplifiedResult); + private getCombinedCodeFix({scope, fixId}, simplifiedResult); private applyCodeActionCommand(commandName, requestSeq, args); private getStartAndEndPosition(args, scriptInfo); private mapCodeAction({description, changes: unmappedChanges, commands}, scriptInfo); diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f8f8101d419c2..108d2f2110893 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3983,8 +3983,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFix[]; - getCombinedCodeFix(fileName: string, actionId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4000,6 +4000,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } @@ -4076,9 +4080,9 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } - interface CodeFix extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this actionId. */ - actionId?: {}; + interface CodeFixAction extends CodeAction { + /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + fixId?: {}; } interface CombinedCodeActions { changes: FileTextChanges[]; diff --git a/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts index ffc5be68609af..f655065e78cbc 100644 --- a/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingInvocationForDecorator_all.ts @@ -10,7 +10,7 @@ ////} verify.codeFixAll({ - actionId: "addMissingInvocationForDecorator", + fixId: "addMissingInvocationForDecorator", newFileContent: `declare function foo(): (...args: any[]) => void; class C { diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all.ts b/tests/cases/fourslash/codeFixAddMissingMember_all.ts index ec19745bdec31..e7edad472272f 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all.ts @@ -9,7 +9,7 @@ ////} verify.codeFixAll({ - actionId: "addMissingMember", + fixId: "addMissingMember", newFileContent: // TODO: GH#18445 `class C { diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts index abe81874a13bb..9c8eb2377e372 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts @@ -14,7 +14,7 @@ ////} verify.codeFixAll({ - actionId: "addMissingMember", + fixId: "addMissingMember", newFileContent: // TODO: GH#18445 GH#20073 `class C { diff --git a/tests/cases/fourslash/codeFixCannotFindModule_all.ts b/tests/cases/fourslash/codeFixCannotFindModule_all.ts index 1ea9423e5bd31..bbe4192c256b9 100644 --- a/tests/cases/fourslash/codeFixCannotFindModule_all.ts +++ b/tests/cases/fourslash/codeFixCannotFindModule_all.ts @@ -21,7 +21,7 @@ test.setTypesRegistry({ goTo.marker(); verify.codeFixAll({ - actionId: "fixCannotFindModule", + fixId: "fixCannotFindModule", commands: [ { packageName: "@types/abs", file: "/a.ts", type: "install package" }, { packageName: "@types/zap", file: "/a.ts", type: "install package" }, diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts index 0fd13059933eb..2c0768c05807c 100644 --- a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts @@ -4,6 +4,6 @@ ////function f(a: ?number, b: string!) {} verify.codeFixAll({ - actionId: "fixJSDocTypes_plain", + fixId: "fixJSDocTypes_plain", newFileContent: "function f(a: number | null, b: string) {}", }) diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts index a7a74909f0e13..5a18fa620f800 100644 --- a/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts @@ -4,6 +4,6 @@ ////function f(a: ?number, b: string!) {} verify.codeFixAll({ - actionId: "fixJSDocTypes_nullable", + fixId: "fixJSDocTypes_nullable", newFileContent: "function f(a: number | null | undefined, b: string) {}", }) diff --git a/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts b/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts index eec691b2cf5df..68743288c71de 100644 --- a/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts +++ b/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts @@ -3,14 +3,18 @@ ////class A { //// f() {} ////} -//// -////let B = class implements A {[| |]} +////let B = class implements A {} verify.codeFix({ description: "Implement interface 'A'", // TODO: GH#18795 - newRangeContent: `f(): void {\r - throw new Error("Method not implemented.");\r -}\r - ` + newFileContent: +`class A { + f() {} +} +let B = class implements A {\r + f(): void {\r + throw new Error("Method not implemented.");\r + }\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExprExtendsAbstractExpressionWithTypeArgs.ts b/tests/cases/fourslash/codeFixClassExprExtendsAbstractExpressionWithTypeArgs.ts index f011ed1cf251a..76117f05090c7 100644 --- a/tests/cases/fourslash/codeFixClassExprExtendsAbstractExpressionWithTypeArgs.ts +++ b/tests/cases/fourslash/codeFixClassExprExtendsAbstractExpressionWithTypeArgs.ts @@ -7,11 +7,20 @@ //// return C; ////} //// -////let B = class extends foo("s") {[| |]} +////let B = class extends foo("s") {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `a: string | number;\r - ` + newFileContent: +`function foo(a: T) { + abstract class C { + abstract a: T | U; + } + return C; +} + +let B = class extends foo("s") {\r + a: string | number;\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractExpressionWithTypeArgs.ts b/tests/cases/fourslash/codeFixClassExtendAbstractExpressionWithTypeArgs.ts index 9095afe9c8402..b7557419de0de 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractExpressionWithTypeArgs.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractExpressionWithTypeArgs.ts @@ -12,6 +12,15 @@ verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `a: string | number;\r - ` + newFileContent: +`function foo(a: T) { + abstract class C { + abstract a: T | U; + } + return C; +} + +class B extends foo("s") {\r + a: string | number;\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts b/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts index 0ef9c9bf20f1b..4949ddbf7c5ba 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts @@ -18,17 +18,37 @@ ////// Don't need to add anything in this case. ////abstract class B extends A {} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `a: string | number;\r -b: this;\r -c: A;\r -d: string | number;\r -e: this;\r -f: A;\r -g: string;\r - ` + newFileContent: +`abstract class A { + private _a: string; + + abstract get a(): number | string; + abstract get b(): this; + abstract get c(): A; + + abstract set d(arg: number | string); + abstract set e(arg: this); + abstract set f(arg: A); + + abstract get g(): string; + abstract set g(newName: string); +} + +// Don't need to add anything in this case. +abstract class B extends A {} + +class C extends A {\r + a: string | number;\r + b: this;\r + c: A;\r + d: string | number;\r + e: this;\r + f: A;\r + g: string;\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts index 4c21038af16d1..462dd1e18eaf3 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts @@ -8,20 +8,30 @@ //// abstract foo(): number; ////} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `f(a: number, b: string): boolean;\r -f(a: number, b: string): this;\r -f(a: string, b: number): Function;\r -f(a: string): Function;\r -f(a: any, b?: any) {\r - throw new Error("Method not implemented.");\r -}\r -foo(): number {\r - throw new Error("Method not implemented.");\r -}\r - ` + newFileContent: +`abstract class A { + abstract f(a: number, b: string): boolean; + abstract f(a: number, b: string): this; + abstract f(a: string, b: number): Function; + abstract f(a: string): Function; + abstract foo(): number; +} + +class C extends A {\r + f(a: number, b: string): boolean;\r + f(a: number, b: string): this;\r + f(a: string, b: number): Function;\r + f(a: string): Function;\r + f(a: any, b?: any) {\r + throw new Error("Method not implemented.");\r + }\r + foo(): number {\r + throw new Error("Method not implemented.");\r + }\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts index 3c5b03033315d..7fa5b76278766 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts @@ -4,13 +4,19 @@ //// abstract f(): this; ////} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `f(): this {\r - throw new Error("Method not implemented.");\r -}\r - ` + newFileContent: +`abstract class A { + abstract f(): this; +} + +class C extends A {\r + f(): this {\r + throw new Error("Method not implemented.");\r + }\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts index b32724f2ee005..01411fb34cb84 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts @@ -4,13 +4,19 @@ //// abstract f(x: T): T; ////} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `f(x: number): number {\r - throw new Error("Method not implemented.");\r -}\r - ` + newFileContent: +`abstract class A { + abstract f(x: T): T; +} + +class C extends A {\r + f(x: number): number {\r + throw new Error("Method not implemented.");\r + }\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts index 8ec76c0953fd5..aff612ac95d7e 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts @@ -4,13 +4,19 @@ //// abstract f(x: T): T; ////} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `f(x: U): U {\r - throw new Error("Method not implemented.");\r -}\r - ` + newFileContent: +`abstract class A { + abstract f(x: T): T; +} + +class C extends A {\r + f(x: U): U {\r + throw new Error("Method not implemented.");\r + }\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts index c68423375fc2c..ba6afb9ac7a77 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts @@ -7,18 +7,20 @@ ////class C extends A {} verify.codeFixAll({ - actionId: "fixClassDoesntImplementInheritedAbstractMember", - // TODO: GH#20073 GH#18445 + fixId: "fixClassDoesntImplementInheritedAbstractMember", + // TODO: GH#18445 newFileContent: `abstract class A { abstract m(): void; } -class B extends A {m(): void {\r - throw new Error("Method not implemented.");\r -}\r +class B extends A {\r + m(): void {\r + throw new Error("Method not implemented.");\r + }\r } -class C extends A {m(): void {\r - throw new Error("Method not implemented.");\r -}\r +class C extends A {\r + m(): void {\r + throw new Error("Method not implemented.");\r + }\r }`, }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts index bc4f26c61163f..6fd77e90385f3 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts @@ -6,13 +6,21 @@ //// abstract z: A; ////} //// -////class C extends A {[| |]} +////class C extends A {} verify.codeFix({ description: "Implement inherited abstract class", // TODO: GH#18795 - newRangeContent: `x: number;\r -y: this;\r -z: A;\r - ` + newFileContent: +`abstract class A { + abstract x: number; + abstract y: this; + abstract z: A; +} + +class C extends A {\r + x: number;\r + y: this;\r + z: A;\r +}` }); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractPropertyThis.ts b/tests/cases/fourslash/codeFixClassExtendAbstractPropertyThis.ts index de128ca1b7990..9531c3b7533a1 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractPropertyThis.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractPropertyThis.ts @@ -1,11 +1,20 @@ /// -//// abstract class A { +////abstract class A { //// abstract x: this; -//// } +////} //// -//// class C extends A {[| |]} +////class C extends A {[| |]} -verify.rangeAfterCodeFix(` - x: this; -`); +verify.codeFix({ + description: "Implement inherited abstract class", + // TODO: GH#18445 + newFileContent: +`abstract class A { + abstract x: this; +} + +class C extends A {\r + x: this;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts index d2532ef16debb..63a3ed99f672b 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts @@ -1,11 +1,20 @@ /// -//// abstract class A { +////abstract class A { //// protected abstract x: number; -//// } +////} //// -//// class C extends A {[| |]} +////class C extends A {[| |]} -verify.rangeAfterCodeFix(` -protected x: number; -`); +verify.codeFix({ + description: "Implement inherited abstract class", + // TODO: GH#18445 + newFileContent: +`abstract class A { + protected abstract x: number; +} + +class C extends A {\r + protected x: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts index 495d661ec79ef..c8696b1ad6cf3 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts @@ -1,12 +1,20 @@ /// -//// abstract class A { +////abstract class A { //// public abstract x: number; -//// } +////} //// -//// class C extends A {[| |]} +////class C extends A {[| |]} +verify.codeFix({ + description: "Implement inherited abstract class", + // TODO: GH#18445 + newFileContent: +`abstract class A { + public abstract x: number; +} -verify.rangeAfterCodeFix(` -public x: number; -`); \ No newline at end of file +class C extends A {\r + public x: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassAbstractGettersAndSetters.ts b/tests/cases/fourslash/codeFixClassImplementClassAbstractGettersAndSetters.ts index df78803c5889e..f6fac956c5b48 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassAbstractGettersAndSetters.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassAbstractGettersAndSetters.ts @@ -1,20 +1,36 @@ /// -//// abstract class A { -//// private _a: string; -//// -//// abstract get a(): string; -//// abstract set a(newName: string); -//// -//// abstract get b(): number; -//// -//// abstract set c(arg: number | string); -//// } +////abstract class A { +//// private _a: string; //// -//// class C implements A {[| |]} +//// abstract get a(): string; +//// abstract set a(newName: string); +//// +//// abstract get b(): number; +//// +//// abstract set c(arg: number | string); +////} +//// +////class C implements A {} + +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`abstract class A { + private _a: string; + + abstract get a(): string; + abstract set a(newName: string); + + abstract get b(): number; + + abstract set c(arg: number | string); +} -verify.rangeAfterCodeFix(` - a: string; - b: number; - c: string | number; -`); \ No newline at end of file +class C implements A {\r + a: string;\r + b: number;\r + c: string | number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts b/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts index b8fdace9f7ede..dbc0689644f07 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts @@ -1,13 +1,22 @@ /// -//// class A { -//// f() {} -//// } +////class A { +//// f() {} +////} //// -//// class B implements A {[| |]} +////class B implements A {[| |]} -verify.rangeAfterCodeFix(` -f(): void{ - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`class A { + f() {} } -`); + +class B implements A {\r + f(): void {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts index 2e8dc316b382e..8c563d4dbb79e 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts @@ -1,14 +1,23 @@ /// -//// class A { -//// method(a: number, b: string): boolean; -//// method(a: string | number, b?: string | number): boolean | Function { return true; } -//// -//// class C implements A {[| |]} +////class A { +//// method(a: number, b: string): boolean; +//// method(a: string | number, b?: string | number): boolean | Function { return true; } +////} +////class C implements A {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`class A { method(a: number, b: string): boolean; - method(a: string | number, b?: string | number): boolean | Function { - throw new Error("Method not implemented."); - } -`); + method(a: string | number, b?: string | number): boolean | Function { return true; } +} +class C implements A {\r + method(a: number, b: string): boolean;\r + method(a: string | number, b?: string | number): boolean | Function {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts index 463b10c450f01..de2bfab561e99 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts @@ -1,18 +1,29 @@ /// -//// class A { -//// method(a: any, b: string): boolean; -//// method(a: string, b: number): Function; -//// method(a: string): Function; -//// method(a: string | number, b?: string | number): boolean | Function { return true; } -//// -//// class C implements A {[| |]} +////class A { +//// method(a: any, b: string): boolean; +//// method(a: string, b: number): Function; +//// method(a: string): Function; +//// method(a: string | number, b?: string | number): boolean | Function { return true; } +////} +////class C implements A {[| |]} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`class A { method(a: any, b: string): boolean; method(a: string, b: number): Function; method(a: string): Function; - method(a: string | number, b?: string | number): boolean | Function { - throw new Error("Method not implemented."); - } -`); + method(a: string | number, b?: string | number): boolean | Function { return true; } +} +class C implements A {\r + method(a: any, b: string): boolean;\r + method(a: string, b: number): Function;\r + method(a: string): Function;\r + method(a: string | number, b?: string | number): boolean | Function {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassPropertyModifiers.ts b/tests/cases/fourslash/codeFixClassImplementClassPropertyModifiers.ts index a40ffc8aeb0f4..f3aa6bc6c6caa 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassPropertyModifiers.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassPropertyModifiers.ts @@ -1,16 +1,28 @@ /// -//// abstract class A { -//// abstract x: number; -//// private y: number; -//// protected z: number; -//// public w: number; -//// } +////abstract class A { +//// abstract x: number; +//// private y: number; +//// protected z: number; +//// public w: number; +////} //// -//// class C implements A {[| |]} +////class C implements A {[| |]} -verify.rangeAfterCodeFix(` -x: number; -protected z: number; -public w: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`abstract class A { + abstract x: number; + private y: number; + protected z: number; + public w: number; +} + +class C implements A {\r + x: number;\r + protected z: number;\r + public w: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts b/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts index 4a5663a8ba84b..0981db995bb47 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts @@ -1,10 +1,18 @@ /// -//// class A { -//// A: typeof A; -//// } -//// class D implements A {[| |]} +////class A { +//// A: typeof A; +////} +////class D implements A {[| |]} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'A'", + // TODO: GH#18445 + newFileContent: +`class A { A: typeof A; -`); +} +class D implements A {\r + A: typeof A;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts b/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts index f11dc29395d7d..ae187b93572e6 100644 --- a/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts +++ b/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts @@ -1,39 +1,37 @@ /// - -//// // Referenced throughout the inheritance chain. -//// interface I0 { a: number } -//// -//// class C1 implements I0 { a: number } -//// interface I1 { b: number } -//// interface I2 extends C1, I1 {} -//// -//// class C2 { c: number } -//// interface I3 {d: number} -//// class C3 extends C2 implements I0, I2, I3 { -//// a: number; -//// b: number; -//// d: number; -//// } -//// -//// interface I4 { e: number } -//// interface I5 { f: number } -//// class C4 extends C3 implements I0, I4, I5 { -//// e: number; -//// f: number; -//// } -//// -//// interface I6 extends C4 {} -//// class C5 implements I6 {[| |]} - +////// Referenced throughout the inheritance chain. +////interface I0 { a: number } +//// +////class C1 implements I0 { a: number } +////interface I1 { b: number } +////interface I2 extends C1, I1 {} +//// +////class C2 { c: number } +////interface I3 {d: number} +////class C3 extends C2 implements I0, I2, I3 { +//// a: number; +//// b: number; +//// d: number; +////} +//// +////interface I4 { e: number } +////interface I5 { f: number } +////class C4 extends C3 implements I0, I4, I5 { +//// e: number; +//// f: number; +////} +//// +////interface I6 extends C4 {} +////class C5 implements I6 {} /** * We want to check whether the search for member to replace actually searches through * the various possible paths of the inheritance chain correctly, and that We * don't issue duplicates for the same member. - * + * * Our class DAG: - * + * * C5 * |-I6 * |-C4 @@ -50,13 +48,39 @@ * |-C2 */ +verify.codeFix({ + description: "Implement interface 'I6'", + // TODO: GH#18445 + newFileContent: +`// Referenced throughout the inheritance chain. +interface I0 { a: number } + +class C1 implements I0 { a: number } +interface I1 { b: number } +interface I2 extends C1, I1 {} + +class C2 { c: number } +interface I3 {d: number} +class C3 extends C2 implements I0, I2, I3 { + a: number; + b: number; + d: number; +} + +interface I4 { e: number } +interface I5 { f: number } +class C4 extends C3 implements I0, I4, I5 { + e: number; + f: number; +} -verify.rangeAfterCodeFix( -` -e: number; -f: number; -a: number; -b: number; -d: number; -c: number; -`); +interface I6 extends C4 {} +class C5 implements I6 {\r + e: number;\r + f: number;\r + a: number;\r + b: number;\r + d: number;\r + c: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementDefaultClass.ts b/tests/cases/fourslash/codeFixClassImplementDefaultClass.ts index 9f507c3dc606c..53fb9378591a9 100644 --- a/tests/cases/fourslash/codeFixClassImplementDefaultClass.ts +++ b/tests/cases/fourslash/codeFixClassImplementDefaultClass.ts @@ -1,9 +1,14 @@ /// -//// interface I { x: number; } -//// -//// export default class implements I {[| |]} +////interface I { x: number; } +////export default class implements I {[| |]} -verify.rangeAfterCodeFix(` -x: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { x: number; } +export default class implements I {\r + x: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts index 4550e5ca31aa6..b121fe54ac735 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts @@ -1,15 +1,26 @@ /// -//// interface I { -//// x: number[]; -//// y: Array; -//// z: [number, string, I]; -//// } +////interface I { +//// x: number[]; +//// y: Array; +//// z: [number, string, I]; +////} //// -//// class C implements I {[| |]} +////class C implements I {[| |]} -verify.rangeAfterCodeFix(` -x: number[]; -y: number[]; -z: [number, string, I]; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + x: number[]; + y: Array; + z: [number, string, I]; +} + +class C implements I {\r + x: number[];\r + y: number[];\r + z: [number, string, I];\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceClassExpression.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceClassExpression.ts index 5b07a582c6cfc..8b811a98d2556 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceClassExpression.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceClassExpression.ts @@ -1,10 +1,15 @@ /// -//// interface I { x: number; } -//// -//// new class implements I {[| |]}; +////interface I { x: number; } +////new class implements I {}; -verify.rangeAfterCodeFix(` -x: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { x: number; } +new class implements I {\r + x: number;\r +};`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts index f082074f16ccb..98a00d43f4cf2 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts @@ -2,26 +2,45 @@ // @lib: es2017 -//// namespace N { -//// /**enum prefix */ -//// export enum /**enum identifier prefix */ E /**open-brace prefix*/ { -//// /* literal prefix */ a /** comma prefix */, -//// /* literal prefix */ b /** comma prefix */, -//// /* literal prefix */ c -//// /** close brace prefix */ } -//// /** interface prefix */ -//// export interface /**interface name prefix */ I /**open-brace prefix*/ { -//// /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a; -//// /** property prefix */ b /** colon prefix */: /** enum prefix */ E; -//// /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */; -//// /**close-brace prefix*/ } -//// /**close-brace prefix*/ } -//// class C implements N.I {[| |]} +////namespace N { +//// /**enum prefix */ +//// export enum /**enum identifier prefix */ E /**open-brace prefix*/ { +//// /* literal prefix */ a /** comma prefix */, +//// /* literal prefix */ b /** comma prefix */, +//// /* literal prefix */ c +//// /** close brace prefix */ } +//// /** interface prefix */ +//// export interface /**interface name prefix */ I /**open-brace prefix*/ { +//// /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a; +//// /** property prefix */ b /** colon prefix */: /** enum prefix */ E; +//// /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */; +//// /**close-brace prefix*/ } +/////**close-brace prefix*/ } +////class C implements N.I {} -verify.rangeAfterCodeFix(` - a: N.E.a; - b: N.E; - foo(a: X): string { - throw new Error("Method not implemented."); - } -`); +verify.codeFix({ + description: "Implement interface 'N.I'", + // TODO: GH#18445 + newFileContent: +`namespace N { + /**enum prefix */ + export enum /**enum identifier prefix */ E /**open-brace prefix*/ { + /* literal prefix */ a /** comma prefix */, + /* literal prefix */ b /** comma prefix */, + /* literal prefix */ c + /** close brace prefix */ } + /** interface prefix */ + export interface /**interface name prefix */ I /**open-brace prefix*/ { + /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a; + /** property prefix */ b /** colon prefix */: /** enum prefix */ E; + /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */; + /**close-brace prefix*/ } +/**close-brace prefix*/ } +class C implements N.I {\r + /** property prefix */ a /** colon prefix */: N.E.a;\r + /** property prefix */ b /** colon prefix */: N.E;\r + /**method signature prefix */ foo /**open angle prefix */(a: X): string {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts index 7d77c5b9af393..6bbe5f4efd1d6 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts @@ -1,21 +1,33 @@ /// -//// interface I { -//// ["foo"](o: any): boolean; -//// ["x"]: boolean; -//// [1](): string; -//// [2]: boolean; -//// } +////interface I { +//// ["foo"](o: any): boolean; +//// ["x"]: boolean; +//// [1](): string; +//// [2]: boolean; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` - ["foo"](o: any): boolean { - throw new Error("Method not implemented."); - } +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + ["foo"](o: any): boolean; ["x"]: boolean; - [1](): string { - throw new Error("Method not implemented."); - } + [1](): string; [2]: boolean; -`); \ No newline at end of file +} + +class C implements I {\r + ["foo"](o: any): boolean {\r + throw new Error("Method not implemented.");\r + }\r + ["x"]: boolean;\r + [1](): string {\r + throw new Error("Method not implemented.");\r + }\r + [2]: boolean;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts index 31fc167bb9712..ab8b7ddfebbae 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts @@ -2,50 +2,70 @@ // @lib: es2017 -//// interface I { -//// [Symbol.hasInstance](o: any): boolean; -//// [Symbol.isConcatSpreadable]: boolean; -//// [Symbol.iterator](): any; -//// [Symbol.match]: boolean; -//// [Symbol.replace](...args); -//// [Symbol.search](str: string): number; -//// [Symbol.species](): Species; -//// [Symbol.split](str: string, limit?: number): string[]; -//// [Symbol.toPrimitive](hint: "number"): number; -//// [Symbol.toPrimitive](hint: "default"): number; -//// [Symbol.toPrimitive](hint: "string"): string; -//// [Symbol.toStringTag]: string; -//// [Symbol.unscopables]: any; -//// } -//// class C implements I {[| |]} +////interface I { +//// [Symbol.hasInstance](o: any): boolean; +//// [Symbol.isConcatSpreadable]: boolean; +//// [Symbol.iterator](): any; +//// [Symbol.match]: boolean; +//// [Symbol.replace](...args); +//// [Symbol.search](str: string): number; +//// [Symbol.species](): Species; +//// [Symbol.split](str: string, limit?: number): string[]; +//// [Symbol.toPrimitive](hint: "number"): number; +//// [Symbol.toPrimitive](hint: "default"): number; +//// [Symbol.toPrimitive](hint: "string"): string; +//// [Symbol.toStringTag]: string; +//// [Symbol.unscopables]: any; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` - [Symbol.hasInstance](o: any): boolean { - throw new Error("Method not implemented."); - } +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + [Symbol.hasInstance](o: any): boolean; [Symbol.isConcatSpreadable]: boolean; - [Symbol.iterator]() { - throw new Error("Method not implemented."); - } + [Symbol.iterator](): any; [Symbol.match]: boolean; - [Symbol.replace](...args: {}) { - throw new Error("Method not implemented."); - } - [Symbol.search](str: string): number { - throw new Error("Method not implemented."); - } - [Symbol.species](): number { - throw new Error("Method not implemented."); - } - [Symbol.split](str: string, limit?: number): {} { - throw new Error("Method not implemented."); - } + [Symbol.replace](...args); + [Symbol.search](str: string): number; + [Symbol.species](): Species; + [Symbol.split](str: string, limit?: number): string[]; [Symbol.toPrimitive](hint: "number"): number; [Symbol.toPrimitive](hint: "default"): number; [Symbol.toPrimitive](hint: "string"): string; - [Symbol.toPrimitive](hint: any) { - throw new Error("Method not implemented."); - } [Symbol.toStringTag]: string; [Symbol.unscopables]: any; -`); +} +class C implements I {\r + [Symbol.hasInstance](o: any): boolean {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.isConcatSpreadable]: boolean;\r + [Symbol.iterator]() {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.match]: boolean;\r + [Symbol.replace](...args: {}) {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.search](str: string): number {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.species](): number {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.split](str: string, limit?: number): {} {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.toPrimitive](hint: "number"): number;\r + [Symbol.toPrimitive](hint: "default"): number;\r + [Symbol.toPrimitive](hint: "string"): string;\r + [Symbol.toPrimitive](hint: any) {\r + throw new Error("Method not implemented.");\r + }\r + [Symbol.toStringTag]: string\;\r + [Symbol.unscopables]: any;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts index e4e1cf3608a13..9e01e080af60b 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts @@ -1,17 +1,32 @@ /// -//// namespace N1 { -//// export interface I1 { -//// f1():string; -//// } -//// } -//// interface I1 { -//// f1(); -//// } +////namespace N1 { +//// export interface I1 { +//// f1():string; +//// } +////} +////interface I1 { +//// f1(); +////} //// -//// class C1 implements N1.I1 {[| |]} +////class C1 implements N1.I1 {} -verify.rangeAfterCodeFix(`f1(): string{ - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'N1.I1'", + // TODO: GH#18445 + newFileContent: +`namespace N1 { + export interface I1 { + f1():string; + } } -`); +interface I1 { + f1(); +} + +class C1 implements N1.I1 {\r + f1(): string {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesBoth.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesBoth.ts index e47eeb6d5c17a..2becd28aa4ff1 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesBoth.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesBoth.ts @@ -1,14 +1,24 @@ /// -//// interface I { -//// [x: number]: I; -//// [y: string]: I; -//// } +////interface I { +//// [x: number]: I; +//// [y: string]: I; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { [x: number]: I; [y: string]: I; -`); \ No newline at end of file +} + +class C implements I {\r + [x: number]: I;\r + [y: string]: I;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts index 82cc53570fee8..ebd0904e88a3c 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts @@ -1,10 +1,18 @@ /// -//// interface I { -//// [x: number]: I; -//// } -//// class C implements I {[| |]} +////interface I { +//// [x: number]: I; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { [x: number]: I; -`); \ No newline at end of file +} +class C implements I {\r + [x: number]: I;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesString.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesString.ts index 9d42faada369d..4cd6cd0c9a3b9 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesString.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesString.ts @@ -1,11 +1,20 @@ /// -//// interface I { -//// [Ƚ: string]: X; -//// } +////interface I { +//// [Ƚ: string]: X; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` - [Ƚ: string]: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + [Ƚ: string]: X; +} + +class C implements I {\r + [Ƚ: string]: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts index ed5ea06ebb636..7e93871c55202 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts @@ -1,10 +1,18 @@ /// -//// interface I { -//// x: keyof X; -//// } -//// class C implements I {[| |]} +////interface I { +//// x: keyof X; +////} +////class C implements I {[| |]} -verify.rangeAfterCodeFix(` -x: keyof Y; -`); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + x: keyof X; +} +class C implements I {\r + x: keyof Y;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts index 7bb230a2dfb6d..83a2c131d9766 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts @@ -1,13 +1,24 @@ /// -//// abstract class C1 { } -//// abstract class C2 { -//// abstract fA(); -//// } -//// interface I1 extends C1, C2 { } -//// class C3 implements I1 {[| |]} +////abstract class C1 { } +////abstract class C2 { +//// abstract fA(); +////} +////interface I1 extends C1, C2 { } +////class C3 implements I1 {[| |]} -verify.rangeAfterCodeFix(`fA(){ - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'I1'", + // TODO: GH#18445 + newFileContent: +`abstract class C1 { } +abstract class C2 { + abstract fA(); } -`); \ No newline at end of file +interface I1 extends C1, C2 { } +class C3 implements I1 {\r + fA() {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts index 64676bce86009..b787d0c271e7e 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts @@ -1,10 +1,18 @@ /// -//// interface I { -//// x: { readonly [K in keyof X]: X[K] }; -//// } -//// class C implements I {[| |]} +////interface I { +//// x: { readonly [K in keyof X]: X[K] }; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` -x: { readonly [K in keyof X]: Y[K]; }; -`); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + x: { readonly [K in keyof X]: X[K] }; +} +class C implements I {\r + x: { readonly [K in keyof X]: Y[K]; };\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberNestedTypeAlias.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberNestedTypeAlias.ts index 5aa5f1a4e5117..fc1217f155668 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberNestedTypeAlias.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberNestedTypeAlias.ts @@ -1,16 +1,25 @@ /// -//// type Either = { val: T } | Error; -//// interface I { -//// x: Either>; -//// foo(x: Either>): void; -//// } -//// class C implements I {[| |]} +////type Either = { val: T } | Error; +////interface I { +//// x: Either>; +//// foo(x: Either>): void; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`type Either = { val: T } | Error; +interface I { x: Either>; - foo(x: Either>): void { - throw new Error("Method not implemented."); - } -`); - + foo(x: Either>): void; +} +class C implements I {\r + x: Either>;\r + foo(x: Either>): void {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts index dbfd623be17fa..4bb20142df34e 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts @@ -2,57 +2,90 @@ // @lib: es2017 -//// /** asdf */ -//// interface I { -//// 1; -//// 2; -//// 3; -//// 4; -//// 5; -//// 6; -//// 7; -//// 8; -//// 9; -//// 10; -//// 11; -//// 12; -//// 13; -//// 14; -//// 15; -//// 16; -//// 17; -//// 18; -//// 19; -//// 20; -//// 21; -//// 22; -//// /** a nice safe prime */ -//// 23; -//// } -//// class C implements I {[| |]} +/////** asdf */ +////interface I { +//// 1; +//// 2; +//// 3; +//// 4; +//// 5; +//// 6; +//// 7; +//// 8; +//// 9; +//// 10; +//// 11; +//// 12; +//// 13; +//// 14; +//// 15; +//// 16; +//// 17; +//// 18; +//// 19; +//// 20; +//// 21; +//// 22; +//// /** a nice safe prime */ +//// 23; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` - 1: any; - 2: any; - 3: any; - 4: any; - 5: any; - 6: any; - 7: any; - 8: any; - 9: any; - 10: any; - 11: any; - 12: any; - 13: any; - 14: any; - 15: any; - 16: any; - 17: any; - 18: any; - 19: any; - 20: any; - 21: any; - 22: any; - 23: any; -`); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`/** asdf */ +interface I { + 1; + 2; + 3; + 4; + 5; + 6; + 7; + 8; + 9; + 10; + 11; + 12; + 13; + 14; + 15; + 16; + 17; + 18; + 19; + 20; + 21; + 22; + /** a nice safe prime */ + 23; +} +class C implements I {\r + 1: any;\r + 2: any;\r + 3: any;\r + 4: any;\r + 5: any;\r + 6: any;\r + 7: any;\r + 8: any;\r + 9: any;\r + 10: any;\r + 11: any;\r + 12: any;\r + 13: any;\r + 14: any;\r + 15: any;\r + 16: any;\r + 17: any;\r + 18: any;\r + 19: any;\r + 20: any;\r + 21: any;\r + 22: any;\r + /** a nice safe prime */\r + 23: any;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberTypeAlias.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberTypeAlias.ts index 44981302a1a60..23f1c79054e3c 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberTypeAlias.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberTypeAlias.ts @@ -1,12 +1,19 @@ /// -//// type MyType = [string, number]; -//// interface I { x: MyType; test(a: MyType): void; } -//// class C implements I {[| |]} +////type MyType = [string, number]; +////interface I { x: MyType; test(a: MyType): void; } +////class C implements I {} -verify.rangeAfterCodeFix(` - x: [string, number]; - test(a: [string, number]): void { - throw new Error("Method not implemented."); - } -`); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`type MyType = [string, number]; +interface I { x: MyType; test(a: MyType): void; } +class C implements I {\r + x: [string, number];\r + test(a: [string, number]): void {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts index b8225f31e04f7..41c8f2254db55 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts @@ -1,13 +1,22 @@ /// -//// interface I { -//// f(x: number, y: this): I -//// } +////interface I { +//// f(x: number, y: this): I +////} //// -//// class C implements I {[| |]} +////class C implements I {[| |]} -verify.rangeAfterCodeFix(` -f(x: number,y: this): I { - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + f(x: number, y: this): I } -`); + +class C implements I {\r + f(x: number, y: this): I {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts index 2eb6c2fabcbab..ce1dfead0f3e4 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts @@ -1,16 +1,26 @@ /// -//// interface I { -//// f(i: any): i is I; -//// f(): this is I; -//// } -//// -//// class C implements I {[| |]} +////interface I { +//// f(i: any): i is I; +//// f(): this is I; +////} +//// +////class C implements I {} -verify.rangeAfterCodeFix(` -f(i: any): i is I; -f(): this is I; -f(i?: any) { - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + f(i: any): i is I; + f(): this is I; } -`); + +class C implements I {\r + f(i: any): i is I;\r + f(): this is I;\r + f(i?: any) {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts index 66cdb1f5454d8..afad257a699b7 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts @@ -1,27 +1,41 @@ /// -//// interface I1 { -//// x: number, -//// y: number -//// z: number; -//// f(), -//// g() -//// h(); -//// } +////interface I1 { +//// x: number, +//// y: number +//// z: number; +//// f(), +//// g() +//// h(); +////} //// -//// class C1 implements I1 {[| |]} +////class C1 implements I1 {} -verify.rangeAfterCodeFix(` -x: number; -y: number; -z: number; -f() { - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'I1'", + // TODO: GH#18445 + newFileContent: +`interface I1 { + x: number, + y: number + z: number; + f(), + g() + h(); } -g() { - throw new Error("Method not implemented."); -} -h() { - throw new Error("Method not implemented."); -} -`); \ No newline at end of file + +class C1 implements I1 {\r + x: number;\r + y: number;\r + z: number;\r + f() {\r + throw new Error("Method not implemented.");\r + }\r + g() {\r + throw new Error("Method not implemented.");\r + }\r + h() {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts index 583dda1305cc6..b8167ccf35b2d 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts @@ -1,18 +1,29 @@ /// -//// interface I { -//// method(a: number, b: string): boolean; -//// method(a: string, b: number): Function; -//// method(a: string): Function; -//// } +////interface I { +//// method(a: number, b: string): boolean; +//// method(a: string, b: number): Function; +//// method(a: string): Function; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { method(a: number, b: string): boolean; method(a: string, b: number): Function; method(a: string): Function; - method(a: any, b?: any) { - throw new Error("Method not implemented."); - } -`); +} + +class C implements I {\r + method(a: number, b: string): boolean;\r + method(a: string, b: number): Function;\r + method(a: string): Function;\r + method(a: any, b?: any) {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts index 132c63ae0fcb0..ee64c1be6bb33 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts @@ -1,18 +1,29 @@ /// -//// interface I { -//// method(a: number, ...b: string[]): boolean; -//// method(a: string, ...b: number[]): Function; -//// method(a: string): Function; -//// } +////interface I { +//// method(a: number, ...b: string[]): boolean; +//// method(a: string, ...b: number[]): Function; +//// method(a: string): Function; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { method(a: number, ...b: string[]): boolean; method(a: string, ...b: number[]): Function; method(a: string): Function; - method(a: any, ...b?: any[]) { - throw new Error("Method not implemented."); - } -`); +} + +class C implements I {\r + method(a: number, ...b: string[]): boolean;\r + method(a: string, ...b: number[]): Function;\r + method(a: string): Function;\r + method(a: any, ...b?: any[]) {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts index b4e9a8ff91e78..9b4e471a5d8bd 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts @@ -1,18 +1,29 @@ /// -//// interface I { -//// method(a: number, ...b: string[]): boolean; -//// method(a: string, b: number): Function; -//// method(a: string): Function; -//// } +////interface I { +//// method(a: number, ...b: string[]): boolean; +//// method(a: string, b: number): Function; +//// method(a: string): Function; +////} //// -//// class C implements I {[| |]} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { method(a: number, ...b: string[]): boolean; method(a: string, b: number): Function; method(a: string): Function; - method(a: any, b?: any, ...rest?: any[]) { - throw new Error("Method not implemented."); - } -`); +} + +class C implements I {\r + method(a: number, ...b: string[]): boolean;\r + method(a: string, b: number): Function;\r + method(a: string): Function;\r + method(a: any, b?: any, ...rest?: any[]) {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts index 1d71ce3c47079..ee9808603cf57 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts @@ -1,15 +1,24 @@ /// -//// namespace N1 { -//// export interface I1 { -//// x: number; -//// } -//// } -//// interface I1 { -//// f1(); -//// } -//// class C1 implements N1.I1 {[| |]} +////namespace N1 { +//// export interface I1 { x: number; } +////} +////interface I1 { +//// f1(); +////} +////class C1 implements N1.I1 {} -verify.rangeAfterCodeFix(` -x: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'N1.I1'", + // TODO: GH#18445 + newFileContent: +`namespace N1 { + export interface I1 { x: number; } +} +interface I1 { + f1(); +} +class C1 implements N1.I1 {\r + x: number;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceOptionalProperty.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceOptionalProperty.ts index 96d8defbcc7ac..ea4f5dbdd91db 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceOptionalProperty.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceOptionalProperty.ts @@ -1,13 +1,21 @@ /// -//// interface IPerson { -//// name: string; -//// birthday?: string; -//// } -//// -//// class Person implements IPerson {[| |]} +////interface IPerson { +//// name: string; +//// birthday?: string; +////} +////class Person implements IPerson {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'IPerson'", + // TODO: GH#18445 + newFileContent: +`interface IPerson { name: string; birthday?: string; -`); \ No newline at end of file +} +class Person implements IPerson {\r + name: string;\r + birthday?: string;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts index 53cfce5ff6e8d..f8fb576800fc9 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts @@ -2,18 +2,30 @@ // @lib: es2017 -//// enum E { a,b,c } -//// interface I { -//// x: E; -//// y: E.a -//// z: symbol; -//// w: object; -//// } -//// class C implements I {[| |]} +////enum E { a,b,c } +////interface I { +//// x: E; +//// y: E.a +//// z: symbol; +//// w: object; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`enum E { a,b,c } +interface I { x: E; - y: E.a; + y: E.a z: symbol; w: object; -`); \ No newline at end of file +} +class C implements I {\r + x: E;\r + y: E.a;\r + z: symbol;\r + w: object;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts index 943824100540a..98a0f374ae774 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts @@ -1,34 +1,56 @@ /// -//// interface I { -//// a0: {}; -//// a1: { (b1: number, c1: string): number; }; -//// a2: (b2: number, c2: string) => number; -//// a3: { (b3: number, c3: string): number, x: number }; -//// -//// a4: { new (b1: number, c1: string): number; }; -//// a5: new (b2: number, c2: string) => number; -//// a6: { new (b3: number, c3: string): number, x: number }; -//// -//// a7: { foo(b7: number, c7: string): number }; -//// -//// a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; }; -//// -//// a9: { (b9: number, c9: string): number; [d9: number]: I }; -//// a10: { (b10: number, c10: string): number; [d10: string]: I }; -//// } -//// class C implements I {[| |]} +////interface I { +//// a0: {}; +//// a1: { (b1: number, c1: string): number; }; +//// a2: (b2: number, c2: string) => number; +//// a3: { (b3: number, c3: string): number, x: number }; +//// +//// a4: { new (b1: number, c1: string): number; }; +//// a5: new (b2: number, c2: string) => number; +//// a6: { new (b3: number, c3: string): number, x: number }; +//// +//// a7: { foo(b7: number, c7: string): number }; +//// +//// a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; }; +//// +//// a9: { (b9: number, c9: string): number; [d9: number]: I }; +//// a10: { (b10: number, c10: string): number; [d10: string]: I }; +////} +////class C implements I {} -verify.rangeAfterCodeFix(` +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { a0: {}; - a1: (b1: number, c1: string) => number; + a1: { (b1: number, c1: string): number; }; a2: (b2: number, c2: string) => number; - a3: { (b3: number, c3: string): number; x: number; }; - a4: new (b1: number, c1: string) => number; + a3: { (b3: number, c3: string): number, x: number }; + + a4: { new (b1: number, c1: string): number; }; a5: new (b2: number, c2: string) => number; - a6: { new (b3: number, c3: string): number; x: number; }; - a7: { foo(b7: number, c7: string): number; }; - a8: { (b81: number, c81: string): number; new (b82: number, c82: string): number; }; - a9: { (b9: number, c9: string): number; [d9: number]: I; }; - a10: { (b10: number, c10: string): number; [d10: string]: I; }; -`); \ No newline at end of file + a6: { new (b3: number, c3: string): number, x: number }; + + a7: { foo(b7: number, c7: string): number }; + + a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; }; + + a9: { (b9: number, c9: string): number; [d9: number]: I }; + a10: { (b10: number, c10: string): number; [d10: string]: I }; +} +class C implements I {\r + a0: {};\r + a1: (b1: number, c1: string) => number;\r + a2: (b2: number, c2: string) => number;\r + a3: { (b3: number, c3: string): number; x: number; };\r + a4: new (b1: number, c1: string) => number;\r + a5: new (b2: number, c2: string) => number;\r + a6: { new(b3: number, c3: string): number; x: number; };\r + a7: { foo(b7: number, c7: string): number; };\r + a8: { (b81: number, c81: string): number; new(b82: number, c82: string): number; };\r + a9: { (b9: number, c9: string): number;[d9: number]: I; };\r + a10: { (b10: number, c10: string): number;[d10: string]: I; };\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts index e29f4501b1869..c8e88bced9fe9 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts @@ -1,12 +1,18 @@ /// -//// namespace N { -//// export interface I { -//// y: I; -//// } -//// } -//// class C1 implements N.I {[| |]} +////namespace N { +//// export interface I { y: I; } +////} +////class C1 implements N.I {} -verify.rangeAfterCodeFix(` -y: N.I; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'N.I'", + // TODO: GH#18445 + newFileContent: +`namespace N { + export interface I { y: I; } +} +class C1 implements N.I {\r + y: N.I;\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts index acccafe0fc372..4b50cf2e69bdf 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts @@ -1,11 +1,18 @@ /// -//// interface I { +////interface I { //// x: { y: T, z: T[] }; -//// } -//// -//// class C implements I {[| |]} +////} +////class C implements I {[| |]} -verify.rangeAfterCodeFix(` - x: { y: number; z: number[]; }; -`); +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { + x: { y: T, z: T[] }; +} +class C implements I {\r + x: { y: number; z: number[]; };\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts index a11101c7f4b0e..6df183d3e445b 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts @@ -1,11 +1,14 @@ /// -//// interface I { -//// x: T; -//// } -//// -//// class C implements I {[| |]} +////interface I { x: T; } +////class C implements I {[| |]} -verify.rangeAfterCodeFix(` - x: number; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { x: T; } +class C implements I {\r + x: number;\r +}` +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts index 298ee0704f298..7be7d21f1e4aa 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts @@ -1,11 +1,14 @@ /// -//// interface I { -//// x: T; -//// } -//// -//// class C implements I {[| |]} +////interface I { x: T; } +////class C implements I {} -verify.rangeAfterCodeFix(` - x: T; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { x: T; } +class C implements I {\r + x: T;\r +}` +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts index 3f1da116f423b..ac4cad8022039 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts @@ -1,11 +1,14 @@ /// -//// interface I { -//// x: T; -//// } -//// -//// class C implements I {[| |]} +////interface I { x: T; } +////class C implements I {} -verify.rangeAfterCodeFix(` - x: U; -`); \ No newline at end of file +verify.codeFix({ + description: "Implement interface 'I'", + // TODO: GH#18445 + newFileContent: +`interface I { x: T; } +class C implements I {\r + x: U;\r +}` +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts index 9bcd4b88456c0..6a93c84ecf06a 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts @@ -1,12 +1,20 @@ /// -//// interface I { -//// f(x: T); -//// } -//// -//// class C implements I {[| |]} +////interface I { +//// f(x: T); +////} +////class C implements I {} -verify.rangeAfterCodeFix(`f(x: T){ - throw new Error("Method not implemented."); +verify.codeFix({ + description: "Implement interface 'I'", + newFileContent: + // TODO: GH#18445 +`interface I { + f(x: T); } -`); \ No newline at end of file +class C implements I {\r + f(x: T) {\r + throw new Error("Method not implemented.");\r + }\r +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassImplementInterface_all.ts b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts index dcc956cc032bd..fb3672a794952 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface_all.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts @@ -6,20 +6,22 @@ ////class D implements J {} verify.codeFixAll({ - actionId: "fixClassIncorrectlyImplementsInterface", + fixId: "fixClassIncorrectlyImplementsInterface", // TODO: GH#20073 GH#18445 newFileContent: `interface I { i(): void; } interface J { j(): void; } -class C implements I, J {i(): void {\r - throw new Error("Method not implemented.");\r -}\r -j(): void {\r - throw new Error("Method not implemented.");\r -}\r +class C implements I, J {\r + i(): void {\r + throw new Error("Method not implemented.");\r + }\r + j(): void {\r + throw new Error("Method not implemented.");\r + }\r } -class D implements J {j(): void {\r - throw new Error("Method not implemented.");\r -}\r +class D implements J {\r + j(): void {\r + throw new Error("Method not implemented.");\r + }\r }`, }); diff --git a/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts index b18a30686fc8a..0b12fb4b03d21 100644 --- a/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts +++ b/tests/cases/fourslash/codeFixClassSuperMustPrecedeThisAccess_all.ts @@ -15,7 +15,7 @@ ////} verify.codeFixAll({ - actionId: "classSuperMustPrecedeThisAccess", + fixId: "classSuperMustPrecedeThisAccess", newFileContent: `class C extends Object { constructor() { super();\r diff --git a/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts index 007b110b7803f..8aa0e2c6adeea 100644 --- a/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts +++ b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - actionId: "constructorForDerivedNeedSuperCall", + fixId: "constructorForDerivedNeedSuperCall", // TODO: GH#18445 newFileContent: `class C extends Object { constructor() {\r diff --git a/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts index 98d5ebb207a3c..334972ea13a8b 100644 --- a/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts +++ b/tests/cases/fourslash/codeFixCorrectQualifiedNameToIndexedAccessType_all.ts @@ -7,7 +7,7 @@ ////const y: Foo.bar = ""; verify.codeFixAll({ - actionId: "correctQualifiedNameToIndexedAccessType", + fixId: "correctQualifiedNameToIndexedAccessType", newFileContent: `interface Foo { bar: string; diff --git a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts index a8a2f4f575425..527ecb668485b 100644 --- a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts +++ b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts @@ -10,7 +10,7 @@ ////x = true; verify.codeFixAll({ - actionId: "disableJsDiagnostics", + fixId: "disableJsDiagnostics", newFileContent: `let x = ""; // @ts-ignore\r diff --git a/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts index 2f3c0cc74134f..db039c92890b6 100644 --- a/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts +++ b/tests/cases/fourslash/codeFixExtendsInterfaceBecomesImplements_all.ts @@ -5,7 +5,7 @@ ////class D extends I {} verify.codeFixAll({ - actionId: "extendsInterfaceBecomesImplements", + fixId: "extendsInterfaceBecomesImplements", newFileContent: `interface I {} class C implements I {} class D implements I {}`, diff --git a/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts index e7e94bac3461a..75ded0290e68e 100644 --- a/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts +++ b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts @@ -9,7 +9,7 @@ ////} verify.codeFixAll({ - actionId: "forgottenThisPropertyAccess", + fixId: "forgottenThisPropertyAccess", newFileContent: `class C { foo: number; diff --git a/tests/cases/fourslash/codeFixInferFromUsage_all.ts b/tests/cases/fourslash/codeFixInferFromUsage_all.ts index 836fc334d8c52..5a6b9d0cbd2ae 100644 --- a/tests/cases/fourslash/codeFixInferFromUsage_all.ts +++ b/tests/cases/fourslash/codeFixInferFromUsage_all.ts @@ -12,7 +12,7 @@ ////} verify.codeFixAll({ - actionId: "inferFromUsage", + fixId: "inferFromUsage", newFileContent: `function f(x: number, y: string) { x += 0; diff --git a/tests/cases/fourslash/codeFixSpelling_all.ts b/tests/cases/fourslash/codeFixSpelling_all.ts index 29232f7833f02..08b73b9ca1b14 100644 --- a/tests/cases/fourslash/codeFixSpelling_all.ts +++ b/tests/cases/fourslash/codeFixSpelling_all.ts @@ -6,7 +6,7 @@ ////} verify.codeFixAll({ - actionId: "fixSpelling", + fixId: "fixSpelling", newFileContent: `function f(s: string) { s.toString(); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts index 817f7c65da857..f42915249e906 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - actionId: "unusedIdentifier_delete", + fixId: "unusedIdentifier_delete", newFileContent: `function f() { }`, diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts index 8030490605f9c..55475321f94a6 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_prefix.ts @@ -8,7 +8,7 @@ ////} verify.codeFixAll({ - actionId: "unusedIdentifier_prefix", + fixId: "unusedIdentifier_prefix", newFileContent: `function f(_a, _b) { const x = 0; // Can't be prefixed, ignored diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 89174cfc64450..acebb9dfc0545 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -283,7 +283,7 @@ declare namespace FourSlashInterface { docCommentTemplateAt(markerName: string | FourSlashInterface.Marker, expectedOffset: number, expectedText: string): void; noDocCommentTemplateAt(markerName: string | FourSlashInterface.Marker): void; rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void; - codeFixAll(options: { actionId: string, newFileContent: string, commands?: {}[] }): void; + codeFixAll(options: { fixId: string, newFileContent: string, commands?: {}[] }): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: FormatCodeOptions): void; rangeIs(expectedText: string, includeWhiteSpace?: boolean): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void; From 8a61761ebc558a6479474f55b10b5ca997b736dc Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 7 Dec 2017 07:20:26 -0800 Subject: [PATCH 09/11] Make API changes internal --- src/server/client.ts | 3 +- src/server/protocol.ts | 16 +++++++- src/services/types.ts | 11 ++++- .../reference/api/tsserverlibrary.d.ts | 41 +------------------ tests/baselines/reference/api/typescript.d.ts | 15 +------ 5 files changed, 30 insertions(+), 56 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index f73fdd0f44c3c..ad10ba8fffb74 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -559,7 +559,8 @@ namespace ts.server { const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - return response.body.map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); + // TODO: GH#20538 shouldn't need cast + return (response.body as protocol.CodeFixAction[]).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 3ada81e07a8cf..01da38761e16b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -101,6 +101,8 @@ namespace ts.server.protocol { GetCodeFixes = "getCodeFixes", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", + // TODO: GH#20538 + /* @internal */ GetCombinedCodeFix = "getCombinedCodeFix", /* @internal */ GetCombinedCodeFixFull = "getCombinedCodeFix-full", @@ -536,11 +538,15 @@ namespace ts.server.protocol { arguments: CodeFixRequestArgs; } + // TODO: GH#20538 + /* @internal */ export interface GetCombinedCodeFixRequest extends Request { command: CommandTypes.GetCombinedCodeFix; arguments: GetCombinedCodeFixRequestArgs; } + // TODO: GH#20538 + /* @internal */ export interface GetCombinedCodeFixResponse extends Response { body: CombinedCodeActions; } @@ -597,11 +603,15 @@ namespace ts.server.protocol { errorCodes?: number[]; } + // TODO: GH#20538 + /* @internal */ export interface GetCombinedCodeFixRequestArgs { scope: GetCombinedCodeFixScope; fixId: {}; } + // TODO: GH#20538 + /* @internal */ export interface GetCombinedCodeFixScope { type: "file"; args: FileRequestArgs; @@ -1590,7 +1600,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeFixAction[]; + body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[] } export interface CodeAction { @@ -1602,11 +1612,15 @@ namespace ts.server.protocol { commands?: {}[]; } + // TODO: GH#20538 + /* @internal */ export interface CombinedCodeActions { changes: FileCodeEdits[]; commands?: {}[]; } + // TODO: GH#20538 + /* @internal */ export interface CodeFixAction extends CodeAction { /** If present, one may call 'getCombinedCodeFix' with this fixId. */ fixId?: {}; diff --git a/src/services/types.ts b/src/services/types.ts index a9d7ca509faec..2d835316f7d80 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -294,7 +294,10 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; + // TODO: GH#20538 return `CodeFixAction[]` + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + // TODO: GH#20538 + /* @internal */ getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; @@ -323,6 +326,8 @@ namespace ts { dispose(): void; } + // TODO: GH#20538 + /* @internal */ export interface CombinedCodeFixScope { type: "file"; fileName: string; } export interface GetCompletionsAtPositionOptions { @@ -412,11 +417,15 @@ namespace ts { commands?: CodeActionCommand[]; } + // TODO: GH#20538 + /* @internal */ export interface CodeFixAction extends CodeAction { /** If present, one may call 'getCombinedCodeFix' with this fixId. */ fixId?: {}; } + // TODO: GH#20538 + /* @internal */ export interface CombinedCodeActions { changes: FileTextChanges[]; commands: CodeActionCommand[] | undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ea17aa4868b32..b3455eb7d84d5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3983,8 +3983,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; - getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4000,10 +3999,6 @@ declare namespace ts { getProgram(): Program; dispose(): void; } - interface CombinedCodeFixScope { - type: "file"; - fileName: string; - } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } @@ -4080,14 +4075,6 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } - interface CodeFixAction extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this fixId. */ - fixId?: {}; - } - interface CombinedCodeActions { - changes: FileTextChanges[]; - commands: CodeActionCommand[] | undefined; - } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } @@ -4929,7 +4916,6 @@ declare namespace ts.server.protocol { DocCommentTemplate = "docCommentTemplate", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", - GetCombinedCodeFix = "getCombinedCodeFix", ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -5277,13 +5263,6 @@ declare namespace ts.server.protocol { command: CommandTypes.GetCodeFixes; arguments: CodeFixRequestArgs; } - interface GetCombinedCodeFixRequest extends Request { - command: CommandTypes.GetCombinedCodeFix; - arguments: GetCombinedCodeFixRequestArgs; - } - interface GetCombinedCodeFixResponse extends Response { - body: CombinedCodeActions; - } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -5317,14 +5296,6 @@ declare namespace ts.server.protocol { */ errorCodes?: number[]; } - interface GetCombinedCodeFixRequestArgs { - scope: GetCombinedCodeFixScope; - fixId: {}; - } - interface GetCombinedCodeFixScope { - type: "file"; - args: FileRequestArgs; - } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; @@ -6067,7 +6038,7 @@ declare namespace ts.server.protocol { } interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeFixAction[]; + body?: CodeAction[]; } interface CodeAction { /** Description of the code action to display in the UI of the editor */ @@ -6077,14 +6048,6 @@ declare namespace ts.server.protocol { /** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */ commands?: {}[]; } - interface CombinedCodeActions { - changes: FileCodeEdits[]; - commands?: {}[]; - } - interface CodeFixAction extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this fixId. */ - fixId?: {}; - } /** * Format and format on key response message. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 108d2f2110893..6120197b791a2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3983,8 +3983,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[]; - getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4000,10 +3999,6 @@ declare namespace ts { getProgram(): Program; dispose(): void; } - interface CombinedCodeFixScope { - type: "file"; - fileName: string; - } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } @@ -4080,14 +4075,6 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } - interface CodeFixAction extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this fixId. */ - fixId?: {}; - } - interface CombinedCodeActions { - changes: FileTextChanges[]; - commands: CodeActionCommand[] | undefined; - } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } From 87d30c0af9a881cffabc9eb6e8613ec526872906 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 7 Dec 2017 09:31:13 -0800 Subject: [PATCH 10/11] Code review --- src/harness/fourslash.ts | 1 + src/server/client.ts | 4 ++-- src/server/protocol.ts | 6 +++--- src/server/session.ts | 4 ++-- src/services/services.ts | 2 +- src/services/types.ts | 8 ++++---- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 13bfb36b3c12f..0d5216d62b503 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2435,6 +2435,7 @@ Actual: ${stringify(fullActual)}`); ts.Debug.assert(ts.contains(fixIds, fixId), "No available code fix has that group id.", () => `Expected '${fixId}'. Available action ids: ${fixIds}`); const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings); assert.deepEqual(commands, options.commands); + assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files"); this.applyChanges(changes); this.verifyCurrentFileContent(newFileContent); } diff --git a/src/server/client.ts b/src/server/client.ts index ad10ba8fffb74..f34516ac4fd92 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -553,14 +553,14 @@ namespace ts.server { return notImplemented(); } - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeFixAction[] { + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: ReadonlyArray): ReadonlyArray { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); // TODO: GH#20538 shouldn't need cast - return (response.body as protocol.CodeFixAction[]).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); + return (response.body as ReadonlyArray).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f3838edd2bf77..f2785ac2d15cb 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -619,7 +619,7 @@ namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[]; + errorCodes?: ReadonlyArray; } // TODO: GH#20538 @@ -1634,8 +1634,8 @@ namespace ts.server.protocol { // TODO: GH#20538 /* @internal */ export interface CombinedCodeActions { - changes: FileCodeEdits[]; - commands?: {}[]; + changes: ReadonlyArray; + commands?: ReadonlyArray<{}>; } // TODO: GH#20538 diff --git a/src/server/session.ts b/src/server/session.ts index 188bc6deb48e3..17c5777132f22 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1545,7 +1545,7 @@ namespace ts.server { } } - private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] { + private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { if (args.errorCodes.length === 0) { return undefined; } @@ -1618,7 +1618,7 @@ namespace ts.server { return { description, changes, commands }; } - private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges[]): protocol.FileCodeEdits[] { + private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray): protocol.FileCodeEdits[] { return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName)))); } diff --git a/src/services/services.ts b/src/services/services.ts index 72aa920b97c29..1660ca10e4347 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1883,7 +1883,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeFixAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); diff --git a/src/services/types.ts b/src/services/types.ts index f9c20cfe9c54c..e968adfad1fa2 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,8 +295,8 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - // TODO: GH#20538 return `CodeFixAction[]` - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + // TODO: GH#20538 return `ReadonlyArray` + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; // TODO: GH#20538 /* @internal */ getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; @@ -428,8 +428,8 @@ namespace ts { // TODO: GH#20538 /* @internal */ export interface CombinedCodeActions { - changes: FileTextChanges[]; - commands: CodeActionCommand[] | undefined; + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; } // Publicly, this type is just `{}`. Internally it is a union of all the actions we use. From 5f7ba73070a756a1cd415300713a01b644121829 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 7 Dec 2017 11:46:29 -0800 Subject: [PATCH 11/11] Update comment --- src/server/protocol.ts | 5 ++++- src/services/types.ts | 5 ++++- tests/baselines/reference/api/tsserverlibrary.d.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f2785ac2d15cb..4bd7a0a0967d7 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1641,7 +1641,10 @@ namespace ts.server.protocol { // TODO: GH#20538 /* @internal */ export interface CodeFixAction extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ fixId?: {}; } diff --git a/src/services/types.ts b/src/services/types.ts index e968adfad1fa2..471ae3b345977 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -421,7 +421,10 @@ namespace ts { // TODO: GH#20538 /* @internal */ export interface CodeFixAction extends CodeAction { - /** If present, one may call 'getCombinedCodeFix' with this fixId. */ + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ fixId?: {}; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3c8af99d5ef63..464e70671f4bd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3984,7 +3984,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -5311,7 +5311,7 @@ declare namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[]; + errorCodes?: ReadonlyArray; } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5df24da05098b..bc7115a089a89 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3984,7 +3984,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise;