diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ec2eff50c3d02..6bc8c1f758b7f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -228,13 +228,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(); @@ -1385,7 +1399,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 0c9747f483727..ee7e9f774d8ba 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 f5cd0c1967e0d..0d5216d62b503 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2376,7 +2376,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) { @@ -2429,6 +2429,17 @@ Actual: ${stringify(fullActual)}`); this.verifyRangeIs(expectedText, includeWhiteSpace); } + public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void { + 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); + assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files"); + 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. @@ -2441,7 +2452,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)) { @@ -2451,7 +2462,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)) { @@ -2490,7 +2501,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.CodeFixAction[] { const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({ start: diagnostic.start, length: diagnostic.length, @@ -2506,7 +2517,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}"`) : ""}`); @@ -2519,8 +2530,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); } @@ -2532,7 +2545,7 @@ Actual: ${stringify(fullActual)}`); this.raiseError("Exactly 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) { @@ -2871,7 +2884,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) { @@ -3038,7 +3051,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)); } @@ -4149,6 +4162,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); } @@ -4584,6 +4601,12 @@ namespace FourSlashInterface { commands?: ts.CodeActionCommand[]; } + export interface VerifyCodeFixAllOptions { + fixId: string; + newFileContent: string; + commands: ReadonlyArray<{}>; + } + export interface VerifyRefactorOptions { name: string; actionName: string; 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/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 59c3a0f829306..393b8b95081cb 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -521,9 +521,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 4439e972c1740..f34516ac4fd92 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -200,7 +200,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 }; } @@ -553,15 +553,18 @@ namespace ts.server { return notImplemented(); } - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] { + 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); - return response.body.map(entry => this.convertCodeActions(entry, file)); + // TODO: GH#20538 shouldn't need cast + return (response.body as ReadonlyArray).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } + getCombinedCodeFix = notImplemented; + applyCodeActionCommand = notImplemented; private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { @@ -638,14 +641,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 2ade527e7c3a8..4bd7a0a0967d7 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -100,9 +100,14 @@ namespace ts.server.protocol { BreakpointStatement = "breakpointStatement", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", - ApplyCodeActionCommand = "applyCodeActionCommand", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", + // TODO: GH#20538 + /* @internal */ + GetCombinedCodeFix = "getCombinedCodeFix", + /* @internal */ + GetCombinedCodeFixFull = "getCombinedCodeFix-full", + ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -552,6 +557,19 @@ 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; + } + export interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -601,7 +619,21 @@ namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[]; + errorCodes?: ReadonlyArray; + } + + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; + fixId: {}; + } + + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; } export interface ApplyCodeActionCommandRequestArgs { @@ -1587,7 +1619,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[] } export interface CodeAction { @@ -1599,6 +1631,23 @@ namespace ts.server.protocol { commands?: {}[]; } + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeActions { + changes: ReadonlyArray; + commands?: ReadonlyArray<{}>; + } + + // TODO: GH#20538 + /* @internal */ + export interface CodeFixAction extends CodeAction { + /** + * 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?: {}; + } + /** * Format and format on key response message. */ diff --git a/src/server/session.ts b/src/server/session.ts index 744563cf321c4..17c5777132f22 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1538,18 +1538,14 @@ 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; } } - 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; } @@ -1571,6 +1567,19 @@ namespace ts.server { } } + 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 }, fixId, formatOptions); + if (simplifiedResult) { + return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; + } + else { + return res; + } + } + private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} { 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 +1614,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: ReadonlyArray): 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)) @@ -1960,6 +1969,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) => { return this.requiredResponse(this.applyCodeActionCommand(request.arguments)); }, diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index ad9ab520dabcf..aa1f2fb6885e7 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): CodeFixAction[] | undefined; + fixIds?: string[]; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } - 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 { + fixId: {}; + } + + export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; + } + export namespace codefix { - const codeFixes: CodeFix[][] = []; - - export function registerCodeFix(codeFix: CodeFix) { - forEach(codeFix.errorCodes, error => { - let fixes = codeFixes[error]; - if (!fixes) { - fixes = []; - codeFixes[error] = fixes; + const codeFixRegistrations: CodeFixRegistration[][] = []; + const fixIdToRegistration = createMap(); + + 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 (reg.fixIds) { + for (const fixId of reg.fixIds) { + Debug.assert(!fixIdToRegistration.has(fixId)); + fixIdToRegistration.set(fixId, reg); + } + } } export function getSupportedErrorCodes() { - return Object.keys(codeFixes); + return Object.keys(codeFixRegistrations); } - export function getFixes(context: CodeFixContext): CodeAction[] { - const fixes = codeFixes[context.errorCode]; - const allActions: CodeAction[] = []; + export function getFixes(context: CodeFixContext): CodeFixAction[] { + const fixes = codeFixRegistrations[context.errorCode]; + const allActions: CodeFixAction[] = []; forEach(fixes, f => { const actions = f.getCodeActions(context); @@ -52,5 +68,40 @@ namespace ts { return allActions; } + + export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { + // Currently fixId is always a string. + return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context); + } + + function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { + 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): CombinedCodeActions { + const commands: CodeActionCommand[] = []; + const changes = textChanges.ChangeTracker.with(context, t => + eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); + return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); + } + + 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 createCombinedCodeActions([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..d063df87be422 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -1,20 +1,22 @@ /* @internal */ namespace ts.codefix { + 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: [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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: 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..3d99b5712485f 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -1,27 +1,36 @@ /* @internal */ namespace ts.codefix { + 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: [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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: (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..6a59e61d52dec 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 fixId = "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)])], + fixId, + }, + { + 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}` + }])], + // fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. + fixId: undefined, + }]; + }, + 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)); + } + }), + }); + + 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 1e18267929fe0..e20bdf0fb5b86 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,208 +1,194 @@ /* @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 fixId = "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, inJs, makeStatic, token, call } = info; + const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs); + const addMember = inJs ? + singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) : + getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic); + return concatenate(singleElementArray(methodCodeAction), addMember); + }, + fixIds: [fixId], + getAllCodeActions: 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, inJs, makeStatic, token, call } = info; + if (!addToSeen(seenNames, token.text)) { + return; + } - function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined { + // Always prefer to add a method declaration if possible. + if (call) { + addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, 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, classDeclaration, token.text, typeNode, makeStatic, newLineCharacter); + } + } + }); + }, + }); - const tokenSourceFile = context.sourceFile; - const start = context.span.start; + interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; 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 inJs = isInJavaScriptFile(classDeclarationSourceFile); + const call = tryCast(token.parent.parent, isCallExpression); - const tokenName = token.getText(tokenSourceFile); + return { token, classDeclaration, makeStatic, classDeclarationSourceFile, 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); - - 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): 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, fixId }; + } - 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, 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)]; + } - (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, 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, fixId }; + } - 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.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, 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, classDeclaration: ClassLikeDeclaration, 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.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, 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.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter); - - actions.push({ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), - changes: indexSignatureChangeTracker.getChanges() - }); - } + 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( + /*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.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter)); + // 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 }; + } - return actions; - } + 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, fixId }; + } - 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.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration, 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, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, newLineCharacter: string, makeStatic: boolean, inJs: boolean) { + const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic); + changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration, newLineCharacter); } } diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 9796bf2fa22c4..2a3736531b2e9 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,34 +1,41 @@ /* @internal */ namespace ts.codefix { + const fixId = "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 => [ + { fixId, ...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. + fixIds: [fixId], + 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)); } - - 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..754735a9d6353 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -1,52 +1,48 @@ /* @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 fixId = "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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: 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); - - // 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); + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const classDeclaration = token.parent; + Debug.assert(isClassLike(classDeclaration)); + return classDeclaration as ClassLikeDeclaration; + } - 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 - }]; - } - } + function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, newLineCharacter: string, changeTracker: textChanges.ChangeTracker): void { + const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); + const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); - return undefined; + // Note that this is ultimately derived from a map indexed by symbol names, + // so duplicates cannot occur. + const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember); + createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member, 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..7076f7da34439 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -1,64 +1,70 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code]; + const fixId = "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, fixId }; + }); + }, + fixIds: [fixId], + getAllCodeActions(context) { + const seenClassDeclarations = createMap(); + return codeFixAll(context, errorCodes, (changes, diag) => { + const classDeclaration = getClass(diag.file!, diag.start!); + if (addToSeen(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; - } - - const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); - const classType = checker.getTypeAtLocation(classDeclaration) as InterfaceType; - const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDeclaration); + function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { + const classDeclaration = getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false)); + Debug.assert(!!classDeclaration); + return classDeclaration!; + } - const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number); - const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String); + 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 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 classType = checker.getTypeAtLocation(classDeclaration); - 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, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member, newLineCharacter)); + function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); - - if (!indexInfoOfKind) { - return; + if (indexInfoOfKind) { + changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration), newLineCharacter); } - 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..9b67c3469cf0b 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -1,49 +1,52 @@ /* @internal */ namespace ts.codefix { + const fixId = "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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions(context) { + const { newLineCharacter, sourceFile } = context; + 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 (addToSeen(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.insertNodeAtConstructorStart(sourceFile, constructor, superCall, 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 d61adfee470e0..4c5b24fb0ec8a 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -1,23 +1,28 @@ /* @internal */ namespace ts.codefix { + const fixId = "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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: 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.insertNodeAtConstructorStart(sourceFile, token.parent, superCall, 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.insertNodeAtConstructorStart(sourceFile, ctr, superCall, newLineCharacter); + } +} diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index 57bf2dd279511..446b0bddfab98 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -1,43 +1,37 @@ /* @internal */ namespace ts.codefix { + const fixId = "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, fixId }]; + }, + 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); + }), + }); - 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..19610da0b1509 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -1,20 +1,26 @@ /* @internal */ namespace ts.codefix { + const fixId = "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, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: 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..8c43ed0cc7fb3 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -1,61 +1,91 @@ /* @internal */ namespace ts.codefix { + const fixIdPlain = "fixJSDocTypes_plain"; + const fixIdNullable = "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, 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), fixIdNullable)); + } + return actions; + + 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)])], + fixId, + }; + } + }, + fixIds: [fixIdPlain, fixIdNullable], + getAllCodeActions(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 && fixId === fixIdNullable ? 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..64cdc3181fb00 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -1,19 +1,34 @@ /* @internal */ namespace ts.codefix { + 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, + ]; 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, fixId }]; + }, + 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); + }), }); - 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 473c474855010..b7d948b3deb4a 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -1,207 +1,235 @@ /* @internal */ namespace ts.codefix { + 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, + ]; 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: 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, fixId: fixIdDelete }); } - switch (token.kind) { - case ts.SyntaxKind.Identifier: - return deleteIdentifierOrPrefixWithUnderscore(token, context.errorCode); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.NamespaceImport: - return [deleteNode(token.parent)]; + 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, fixId: fixIdPrefix }); + } + return result; + }, + fixIds: [fixIdPrefix, fixIdDelete], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const { sourceFile } = context; + const token = getToken(diag.file!, diag.start!); + switch (context.fixId) { + case fixIdPrefix: + if (isIdentifier(token) && canPrefix(token)) { + tryPrefixDeclaration(changes, diag.code, sourceFile, token); + } + break; + case fixIdDelete: + tryDeleteDeclaration(changes, sourceFile, token); + break; default: - return deleteDefault(); + Debug.fail(JSON.stringify(context.fixId)); } + }), + }); - 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, errorCode: number, sourceFile: SourceFile, token: Node): void { + // Don't offer to prefix a property. + if (errorCode !== Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code && 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, errorCode: number): 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; - const deleteAction = functionDeclaration.parameters.length === 1 ? deleteNode(parent) : deleteNodeInList(parent); - return errorCode === Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code - ? [deleteAction] - : [deleteAction, 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..bd2710244e696 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: ClassElement) => 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; @@ -63,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)); @@ -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,71 @@ 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()); + outputMethod(signature, modifiers, name, createStubbedMethodBody()); + break; } - const signatureDeclarations: MethodDeclaration[] = []; for (const signature of signatures) { - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + // 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); - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + outputMethod(signature, modifiers, name, 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; - } - return signatureDeclaration; + 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); } } - 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); - } + 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; } - const newMethod = createMethod( + 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 { + 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 +130,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 +157,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 +181,7 @@ namespace ts.codefix { /*returnType*/ undefined); } - export function createStubbedMethod( + function createStubbedMethod( modifiers: ReadonlyArray, name: PropertyName, optional: boolean, @@ -258,7 +210,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 e786a77c6b6d9..05a92edf12aff 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 + fixIds: [], + getAllCodeActions: 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 CodeFixAction { kind: ImportCodeActionKind; moduleSpecifier?: string; } @@ -155,6 +158,8 @@ namespace ts.codefix { return { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), changes, + // TODO: GH#20315 + fixId: undefined, kind, moduleSpecifier }; diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 3f03180360d2e..9782619d41dd2 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1,270 +1,256 @@ /* @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 fixId = "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 }], fixId }]; + }, + fixIds: [fixId], + getAllCodeActions(context) { + const { sourceFile, program, cancellationToken } = context; + 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); + }); + }, }); - 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?: Map): 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 || addToSeen(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); - - if (!typeString) { - return undefined; - } + return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined; - return createCodeActions(declaration.name.getText(), declaration.name.getEnd(), `: ${typeString}`); - } - - function getCodeActionForVariableUsage(token: Identifier) { - const symbol = checker.getSymbolAtLocation(token); - return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(symbol.valueDeclaration); + default: + throw Debug.fail(String(errorCode)); } + } - 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 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 getCodeActionForParameters(parameterDeclaration: ParameterDeclaration): CodeAction[] { - if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { - return undefined; - } - - const types = inferTypeForParametersFromUsage(containingFunction) || - map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name)); + 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); + } - if (!types) { - return undefined; - } + 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; + } - 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 getCodeActionForParameters(parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { + return undefined; } - function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration) { - const setAccessorParameter = setAccessorDeclaration.parameters[0]; - if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) { - return undefined; - } + const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || + containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, sourceFile, program, cancellationToken) : undefined); + if (!types) return undefined; - const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name) || - inferTypeForVariableFromUsage(setAccessorParameter.name); - const typeString = type && typeToString(type, containingFunction); - if (!typeString) { - return undefined; - } + const textChanges = arrayFrom(mapDefinedIterator(zipToIterator(containingFunction.parameters, types), ([parameter, type]) => + type && !parameter.type && !parameter.initializer ? makeChange(containingFunction, parameter.end, type, program) : undefined)); + return textChanges.length ? { declaration: parameterDeclaration, textChanges } : undefined; + } - return createCodeActions(setAccessorDeclaration.name.getText(), setAccessorParameter.name.getEnd(), `: ${typeString}`); + 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 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 type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, sourceFile, program, cancellationToken) || + inferTypeForVariableFromUsage(setAccessorParameter.name, sourceFile, program, cancellationToken); + return makeFix(setAccessorParameter, setAccessorParameter.name.getEnd(), type, program); + } - const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken); - return createCodeActions(getAccessorDeclaration.name.getText(), closeParenToken.getEnd(), `: ${typeString}`); + function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(getAccessorDeclaration.name)) { + return undefined; } - 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 - }] - }] - }]; - } + const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name, sourceFile, program, cancellationToken); + const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken); + return makeFix(getAccessorDeclaration, closeParenToken.getEnd(), type, program); + } - function getReferences(token: PropertyName | Token) { - const references = FindAllReferences.findReferencedSymbols( - program, - cancellationToken, - program.getSourceFiles(), - token.getSourceFile(), - token.getStart()); + function makeFix(declaration: Declaration, start: number, type: Type | undefined, program: Program): Fix | undefined { + return type && { declaration, textChanges: [makeChange(declaration, start, type, program)] }; + } - Debug.assert(!!references, "Found no references!"); - Debug.assert(references.length === 1, "Found more references than expected"); + 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}` }; + } - return map(references[0].references, r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); - } + function getReferences(token: PropertyName | Token, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Identifier[] { + const references = FindAllReferences.findReferencedSymbols( + program, + cancellationToken, + program.getSourceFiles(), + sourceFile, + token.getStart(sourceFile)); - function inferTypeForVariableFromUsage(token: Identifier) { - return InferFromReference.inferTypeFromReferences(getReferences(token), checker, cancellationToken); - } + Debug.assert(!!references, "Found no references!"); + Debug.assert(references.length === 1, "Found more references than expected"); - 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); - } - } - } + return references[0].references.map(r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); + } - 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 inferTypeForVariableFromUsage(token: Identifier, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Type | undefined { + return InferFromReference.inferTypeFromReferences(getReferences(token, sourceFile, program, cancellationToken), program.getTypeChecker(), cancellationToken); + } - function typeToString(type: Type, enclosingDeclaration: Declaration) { - const writer = getTypeAccessiblityWriter(); - checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); - return writer.string(); + function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | 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 getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) { - for (const child of node.getChildren(sourcefile)) { - if (child.kind === kind) return child; - } - return undefined; - } + 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 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 770d42dd3dd21..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): CodeAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); @@ -1896,6 +1896,16 @@ namespace ts { }); } + 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({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + } + function applyCodeActionCommand(action: CodeActionCommand): Promise; function applyCodeActionCommand(action: CodeActionCommand[]): Promise; function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -2190,6 +2200,7 @@ namespace ts { isValidBraceCompletionAtPosition, getSpanOfEnclosingComment, getCodeFixesAtPosition, + getCombinedCodeFix, applyCodeActionCommand, getEmitOutput, getNonBoundSourceFile, diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 94a928c9cf1fc..ee4b9091565ed 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -194,6 +194,9 @@ 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`. + // Map from class id to nodes to insert at the start + private readonly nodesInsertedAtClassStarts = createMap<{ sourceFile: SourceFile, cls: ClassLikeDeclaration, members: ClassElement[] }>(); public static fromContext(context: TextChangesContext): ChangeTracker { return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext); @@ -220,14 +223,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 +248,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 +264,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; @@ -350,11 +364,15 @@ namespace ts.textChanges { public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement, newLineCharacter: string): void { const firstMember = firstOrUndefined(cls.members); if (!firstMember) { - const members = [newElement]; - const newCls = cls.kind === SyntaxKind.ClassDeclaration - ? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members) - : updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members); - this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true }); + const id = getNodeId(cls).toString(); + const newMembers = this.nodesInsertedAtClassStarts.get(id); + if (newMembers) { + Debug.assert(newMembers.sourceFile === sourceFile && newMembers.cls === cls); + newMembers.members.push(newElement); + } + else { + this.nodesInsertedAtClassStarts.set(id, { sourceFile, cls, members: [newElement] }); + } } else { this.insertNodeBefore(sourceFile, firstMember, newElement, { suffix: newLineCharacter }); @@ -526,7 +544,18 @@ namespace ts.textChanges { return this; } + private finishInsertNodeAtClassStart(): void { + this.nodesInsertedAtClassStarts.forEach(({ sourceFile, cls, members }) => { + const newCls = cls.kind === SyntaxKind.ClassDeclaration + ? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members) + : updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members); + this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true }); + }); + } + public getChanges(): FileTextChanges[] { + this.finishInsertNodeAtClassStart(); + const changesPerFile = createMap(); // group changes per file for (const c of this.changes) { diff --git a/src/services/types.ts b/src/services/types.ts index 272ae19f0075b..471ae3b345977 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,7 +295,11 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - 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; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -323,6 +327,10 @@ namespace ts { dispose(): void; } + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeFixScope { type: "file"; fileName: string; } + export interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; } @@ -410,6 +418,23 @@ namespace ts { commands?: CodeActionCommand[]; } + // TODO: GH#20538 + /* @internal */ + export interface CodeFixAction extends CodeAction { + /** + * 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?: {}; + } + + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | 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..358db5e8b4b99 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1099,6 +1099,24 @@ 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 addToSeen(seen: Map, key: string | number): boolean { + key = String(key); + 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 @@ -1320,15 +1338,6 @@ namespace ts { return position; } - export function getOpenBrace(constructor: ConstructorDeclaration, sourceFile: SourceFile) { - // First token is the open curly, this is where we want to put the 'super' call. - return constructor.body.getFirstToken(sourceFile); - } - - export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { - return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false); - } - export function getSourceFileImportLocation({ text }: SourceFile) { const shebang = getShebang(text); let position = 0; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1f6dadb8b2a38..464e70671f4bd 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; } @@ -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. */ @@ -7089,10 +7089,12 @@ declare namespace ts.server { private getApplicableRefactors(args); private getEditsForRefactor(args, simplifiedResult); private getCodeFixes(args, simplifiedResult); + private getCombinedCodeFix({scope, fixId}, simplifiedResult); private applyCodeActionCommand(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 84b7d9a19be95..bc7115a089a89 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; } @@ -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; 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..f655065e78cbc --- /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({ + fixId: "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..e7edad472272f --- /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({ + fixId: "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..9c8eb2377e372 --- /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({ + fixId: "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..bbe4192c256b9 --- /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({ + fixId: "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..2c0768c05807c --- /dev/null +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all.ts @@ -0,0 +1,9 @@ +/// + +// @strict: true +////function f(a: ?number, b: string!) {} + +verify.codeFixAll({ + 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 new file mode 100644 index 0000000000000..5a18fa620f800 --- /dev/null +++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax_all_nullable.ts @@ -0,0 +1,9 @@ +/// + +// @strict: true +////function f(a: ?number, b: string!) {} + +verify.codeFixAll({ + 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 new file mode 100644 index 0000000000000..ba6afb9ac7a77 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod_all.ts @@ -0,0 +1,26 @@ +/// + +////abstract class A { +//// abstract m(): void; +////} +////class B extends A {} +////class C extends A {} + +verify.codeFixAll({ + fixId: "fixClassDoesntImplementInheritedAbstractMember", + // TODO: GH#18445 + newFileContent: +`abstract class A { + abstract m(): void; +} +class B extends A {\r + 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 new file mode 100644 index 0000000000000..fb3672a794952 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterface_all.ts @@ -0,0 +1,27 @@ +/// + +////interface I { i(): void; } +////interface J { j(): void; } +////class C implements I, J {} +////class D implements J {} + +verify.codeFixAll({ + fixId: "fixClassIncorrectlyImplementsInterface", + // TODO: GH#20073 GH#18445 + newFileContent: +`interface I { i(): void; } +interface J { j(): void; } +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 {\r + 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..0b12fb4b03d21 --- /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({ + fixId: "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..8aa0e2c6adeea --- /dev/null +++ b/tests/cases/fourslash/codeFixConstructorForDerivedNeedSuperCall_all.ts @@ -0,0 +1,23 @@ +/// + +////class C extends Object { +//// constructor() {} +////} +////class D extends Object { +//// constructor() {} +////} + +verify.codeFixAll({ + fixId: "constructorForDerivedNeedSuperCall", + // TODO: GH#18445 + newFileContent: `class C extends Object { + constructor() {\r + super();\r + } +} +class D extends Object { + constructor() {\r + 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..334972ea13a8b --- /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({ + fixId: "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..527ecb668485b --- /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({ + fixId: "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..db039c92890b6 --- /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({ + fixId: "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..75ded0290e68e --- /dev/null +++ b/tests/cases/fourslash/codeFixForgottenThisPropertyAccess_all.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// foo: number; +//// m() { +//// foo; +//// foo; +//// } +////} + +verify.codeFixAll({ + fixId: "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..5a6b9d0cbd2ae --- /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({ + fixId: "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..08b73b9ca1b14 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpelling_all.ts @@ -0,0 +1,15 @@ +/// + +////function f(s: string) { +//// s.toStrang(); +//// s.toStrong(); +////} + +verify.codeFixAll({ + fixId: "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..f42915249e906 --- /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({ + fixId: "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..55475321f94a6 --- /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({ + 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 fbcfd13eea409..acebb9dfc0545 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: { 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; diff --git a/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts b/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts index 8aa0b92fb3be4..28a84d6498b74 100644 --- a/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts +++ b/tests/cases/fourslash/unusedParameterInConstructor1AddUnderscore.ts @@ -1,12 +1,15 @@ /// // @noUnusedParameters: true -//// class C1 { -//// [|constructor(p1: string, public p2: boolean, public p3: any, p5) |] { p5; } -//// } +////class C1 { +//// constructor(p1: string, public p2: boolean, public p3: any, p5) { p5; } +////} verify.codeFix({ description: "Prefix 'p1' with an underscore", index: 1, - newRangeContent: "constructor(_p1: string, public p2: boolean, public p3: any, p5)", + newFileContent: +`class C1 { + constructor(_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 cbc05d6a372f7..a8c12df14d0e3 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 cbfd4247508f4..d22e28d474add 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 4fcb07e8698c4..96d8bfccc85e3 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 379adf720bfc0..536374c6c5555 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"]) {}', }); -