From 94ea825f95db397756129575ea9f7cd4df347f7c Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 5 Aug 2016 16:20:16 -0700 Subject: [PATCH 01/20] Api Changes and simple superfixes --- src/services/codefixes/codeFixProvider.ts | 53 ++++++++++++++++++ src/services/codefixes/references.ts | 6 +++ src/services/codefixes/superFixes.ts | 65 +++++++++++++++++++++++ src/services/services.ts | 49 ++++++++++++++++- src/services/shims.ts | 19 +++++++ src/services/tsconfig.json | 5 +- 6 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 src/services/codefixes/codeFixProvider.ts create mode 100644 src/services/codefixes/references.ts create mode 100644 src/services/codefixes/superFixes.ts diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts new file mode 100644 index 0000000000000..ca877aef46aca --- /dev/null +++ b/src/services/codefixes/codeFixProvider.ts @@ -0,0 +1,53 @@ +/* @internal */ +namespace ts { + export interface CodeFix { + name: string; + errorCodes: string[]; + getCodeActions(context: CodeFixContext): CodeAction[]; + } + + export interface CodeFixContext { + errorCode: string; + sourceFile: SourceFile; + span: TextSpan; + checker: TypeChecker; + newLineCharacter: string; + } + + export namespace codeFix { + const codeFixes: Map = {}; + + export function registerCodeFix(action: CodeFix) { + forEach(action.errorCodes, error => { + let fixes = codeFixes[error]; + if (!fixes) { + fixes = []; + codeFixes[error] = fixes; + } + fixes.push(action); + }); + } + + export class CodeFixProvider { + public static getSupportedErrorCodes() { + return getKeys(codeFixes); + } + + public getFixes(context: CodeFixContext): CodeAction[] { + const fixes = codeFixes[context.errorCode]; + let allActions: CodeAction[] = []; + + Debug.assert(fixes && fixes.length > 0, "No fixes found for error: '${errorCode}'."); + + forEach(fixes, f => { + const actions = f.getCodeActions(context); + if (actions && actions.length > 0) { + allActions = allActions.concat(actions); + } + }); + + return allActions; + } + } + } +} \ No newline at end of file diff --git a/src/services/codefixes/references.ts b/src/services/codefixes/references.ts new file mode 100644 index 0000000000000..3675d626678e3 --- /dev/null +++ b/src/services/codefixes/references.ts @@ -0,0 +1,6 @@ +/// +/// +/// +/// +/// +/// diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts new file mode 100644 index 0000000000000..2c6a67de513b2 --- /dev/null +++ b/src/services/codefixes/superFixes.ts @@ -0,0 +1,65 @@ +/* @internal */ +namespace ts.codeFix { + function getOpenBraceEnd(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).getEnd(); + } + + registerCodeFix({ + name: "AddMissingSuperCallFix", + errorCodes: ["TS2377"], + getCodeActions: (context: CodeFixContext) => { + const sourceFile = context.sourceFile; + const token = getTokenAtPosition(sourceFile, context.span.start); + Debug.assert(token.kind === SyntaxKind.ConstructorKeyword, "Failed to find the constructor."); + + const newPosition = getOpenBraceEnd(token.parent, sourceFile); + return [{ + description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), + changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }] + }]; + } + }); + + registerCodeFix({ + name: "MakeSuperCallTheFirstStatementInTheConstructor", + errorCodes: ["TS17009"], + getCodeActions: (context: CodeFixContext) => { + const sourceFile = context.sourceFile; + + const token = getTokenAtPosition(sourceFile, context.span.start); + const constructor = getContainingFunction(token); + Debug.assert(constructor.kind === SyntaxKind.Constructor, "Failed to find the constructor."); + + const superCall = findSuperCall((constructor).body); + Debug.assert(!!superCall, "Failed to find super call."); + + const newPosition = getOpenBraceEnd(constructor, sourceFile); + const changes = [{ + fileName: sourceFile.fileName, textChanges: [{ + newText: superCall.getText(sourceFile), + span: { start: newPosition, length: 0 } + }, + { + newText: "", + span: { start: superCall.getStart(sourceFile), length: superCall.getWidth(sourceFile) } + }] + }]; + + return [{ + description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), + changes + }]; + + function findSuperCall(n: Node): Node { + if (n.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((n).expression)) { + return n; + } + if (isFunctionLike(n)) { + return undefined; + } + return forEachChild(n, findSuperCall); + } + } + }); +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index aea4d5f872e03..b189a6375b72f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -11,6 +11,7 @@ /// /// /// +/// namespace ts { /** The version of the language service API */ @@ -1237,6 +1238,8 @@ namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[]; + getEmitOutput(fileName: string): EmitOutput; getProgram(): Program; @@ -1283,6 +1286,18 @@ namespace ts { newText: string; } + export interface FileTextChanges { + fileName: string; + textChanges: TextChange[]; + } + + export interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileTextChanges[]; + } + export interface TextInsertion { newText: string; /** The position in newText the caret should point to after the insertion. */ @@ -1886,9 +1901,13 @@ namespace ts { }; } - // Cache host information about script should be refreshed + export function getSupportedCodeFixes() { + return codeFix.CodeFixProvider.getSupportedErrorCodes(); + } + + // Cache host information about script Should be refreshed // at each language service public entry point, since we don't know when - // set of scripts handled by the host changes. + // the set of scripts handled by the host changes. class HostCache { private fileNameToEntry: FileMap; private _compilationSettings: CompilerOptions; @@ -3022,6 +3041,7 @@ namespace ts { documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); + const codeFixProvider: codeFix.CodeFixProvider = new codeFix.CodeFixProvider(); let ruleProvider: formatting.RulesProvider; let program: Program; let lastProjectVersion: string; @@ -7832,6 +7852,30 @@ namespace ts { return []; } + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const checker = program.getTypeChecker(); + let allFixes: CodeAction[] = []; + + forEach(errorCodes, error => { + const context = { + errorCode: error, + sourceFile: sourceFile, + span: { start, length: end - start }, + checker: checker, + newLineCharacter: getNewLineOrDefaultFromHost(host) + }; + + const fixes = codeFixProvider.getFixes(context); + if (fixes) { + allFixes = allFixes.concat(fixes); + } + }); + + return allFixes; + } + /** * Checks if position points to a valid position to add JSDoc comments, and if so, * returns the appropriate template. Otherwise returns an empty string. @@ -8302,6 +8346,7 @@ namespace ts { getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, isValidBraceCompletionAtPosition, + getCodeFixesAtPosition, getEmitOutput, getNonBoundSourceFile, getProgram diff --git a/src/services/shims.ts b/src/services/shims.ts index 45c4b284ae744..dfa34d29acca8 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -240,6 +240,8 @@ namespace ts { */ isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string): string; + getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -255,6 +257,7 @@ namespace ts { getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; discoverTypings(discoverTypingsJson: string): string; + getSupportedCodeFixes(): string; } function logInternalError(logger: Logger, err: Error) { @@ -901,6 +904,16 @@ namespace ts { ); } + public getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string): string { + return this.forwardJSONCall( + `getCodeFixesAtPosition( '${fileName}', ${start}, ${end}, ${errorCodes}')`, + () => { + const localErrors: string[] = JSON.parse(errorCodes); + return this.languageService.getCodeFixesAtPosition(fileName, start, end, localErrors); + } + ); + } + /// NAVIGATE TO /** Return a list of symbols that are interesting to navigate to */ @@ -1109,6 +1122,12 @@ namespace ts { info.compilerOptions); }); } + + public getSupportedCodeFixes(): string { + return this.forwardJSONCall("getSupportedCodeFixes()", + () => getSupportedCodeFixes() + ); + } } export class TypeScriptServicesFactory implements ShimFactory { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index cfeb7c2fcd582..2cf75daa8610e 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -52,6 +52,9 @@ "formatting/rulesMap.ts", "formatting/rulesProvider.ts", "formatting/smartIndenter.ts", - "formatting/tokenRange.ts" + "formatting/tokenRange.ts", + "codeFixes/codeFixProvider.ts", + "codeFixes/references.ts", + "codeFixes/superFixes.ts" ] } From 466d26fc76ff9808383a8973bab4e662ae9b056b Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 5 Aug 2016 16:21:46 -0700 Subject: [PATCH 02/20] Fourslash support --- src/compiler/core.ts | 9 +++++ src/compiler/diagnosticMessages.json | 28 +++++++++++++++ src/compiler/types.ts | 1 + src/harness/fourslash.ts | 51 +++++++++++++++++++++++++-- src/harness/harnessLanguageService.ts | 3 ++ src/server/client.ts | 4 +++ tests/cases/fourslash/fourslash.ts | 1 + 7 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6c87ad82955c3..b07c21fa4cb6d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -115,6 +115,15 @@ namespace ts { return -1; } + export function firstOrUndefined(array: T[], predicate: (x: T) => boolean): T { + for (let i = 0, len = array.length; i < len; i++) { + if (predicate(array[i])) { + return array[i]; + } + } + return undefined; + } + export function indexOfAnyCharCode(text: string, charCodes: number[], start?: number): number { for (let i = start || 0, len = text.length; i < len; i++) { if (contains(charCodes, text.charCodeAt(i))) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8126d5c605ea7..1dffc35c74c17 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3031,5 +3031,33 @@ "Unknown typing option '{0}'.": { "category": "Error", "code": 17010 + }, + "Add missing 'super()' call.": { + "category": "CodeFix", + "code": 90001 + }, + "Make 'super()' call the first statement in the constructor.": { + "category": "CodeFix", + "code": 90002 + }, + "Change 'extends' to 'implements'": { + "category": "CodeFix", + "code": 90003 + }, + "Remove unused identifiers": { + "category": "CodeFix", + "code": 90004 + }, + "Implement interface on reference": { + "category": "CodeFix", + "code": 90005 + }, + "Implement interface on class": { + "category": "CodeFix", + "code": 90006 + }, + "Implement inherited abstract class": { + "category": "CodeFix", + "code": 90007 } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28ededebb0de1..e31a4b0ace188 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2547,6 +2547,7 @@ namespace ts { Warning, Error, Message, + CodeFix, } export enum ModuleResolutionKind { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc60909..91597d7f5931e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -384,7 +384,7 @@ namespace FourSlash { if (exists !== negative) { this.printErrorLog(negative, this.getAllDiagnostics()); - throw new Error("Failure between markers: " + startMarkerName + ", " + endMarkerName); + throw new Error(`Failure between markers: '${startMarkerName}', '${endMarkerName}'`); } } @@ -638,7 +638,6 @@ namespace FourSlash { } } - public verifyCompletionListAllowsNewIdentifier(negative: boolean) { const completions = this.getCompletionListAtCaret(); @@ -1479,7 +1478,7 @@ namespace FourSlash { if (isFormattingEdit) { const newContent = this.getFileContent(fileName); - if (newContent.replace(/\s/g, "") !== oldContent.replace(/\s/g, "")) { + if (this.removeWhitespace(newContent) !== this.removeWhitespace(oldContent)) { this.raiseError("Formatting operation destroyed non-whitespace content"); } } @@ -1545,6 +1544,10 @@ namespace FourSlash { } } + private removeWhitespace(text: string): string { + return text.replace(/\s/g, ""); + } + public goToBOF() { this.goToPosition(0); } @@ -1862,6 +1865,44 @@ namespace FourSlash { } } + public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) { + + const ranges = this.getRanges(); + if (ranges.length == 0) { + this.raiseError("At least one range should be specified in the testfile."); + } + + const fileName = this.activeFile.fileName; + const diagnostics = this.getDiagnostics(fileName); + + if (diagnostics.length === 0) { + this.raiseError("Errors expected."); + } + + if (diagnostics.length > 1 && !errorCode) { + this.raiseError("When there's more than one error, you must specify the errror to fix."); + } + + const diagnostic = !errorCode ? diagnostics[0] : ts.firstOrUndefined(diagnostics, d => d.code == errorCode); + + const actual = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [`TS${diagnostic.code}`]); + + if (!actual || actual.length == 0) { + this.raiseError("No codefixes returned."); + } + + if (actual.length > 1) { + this.raiseError("More than 1 codefix returned."); + } + + this.applyEdits(actual[0].changes[0].fileName, actual[0].changes[0].textChanges, /*isFormattingEdit*/ false); + const actualText = this.rangeText(ranges[0]); + + if (this.removeWhitespace(actualText) !== this.removeWhitespace(expectedText)) { + this.raiseError(`Actual text doesn't match expected text. Actual: '${actualText}' Expected: '${expectedText}'`); + } + } + public verifyDocCommentTemplate(expected?: ts.TextInsertion) { const name = "verifyDocCommentTemplate"; const actual = this.languageService.getDocCommentTemplateAtPosition(this.activeFile.fileName, this.currentCaretPosition); @@ -3066,6 +3107,10 @@ namespace FourSlashInterface { this.DocCommentTemplate(/*expectedText*/ undefined, /*expectedOffset*/ undefined, /*empty*/ true); } + public codeFixAtPosition(expectedText: string, errorCode?: number): void { + this.state.verifyCodeFixAtPosition(expectedText, errorCode); + } + public navigationBar(json: any) { this.state.verifyNavigationBar(json); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d7ed04b627f4f..d2219c3969d6e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -453,6 +453,9 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): ts.CodeAction[] { + return unwrapJSONCallResult(this.shim.getCodeFixesAtPosition(fileName, start, end, JSON.stringify(errorCodes))); + } getEmitOutput(fileName: string): ts.EmitOutput { return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); } diff --git a/src/server/client.ts b/src/server/client.ts index f04dbd8dc0253..3c8a07c8cdf23 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -592,6 +592,10 @@ namespace ts.server { throw new Error("Not Implemented Yet."); } + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): ts.CodeAction[] { + throw new Error("Not Implemented Yet."); + } + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { const lineOffset = this.positionToOneBasedLineOffset(fileName, position); const args: protocol.FileLocationRequestArgs = { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 9ce3197a46f73..ee0d4d100d3ca 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -192,6 +192,7 @@ declare namespace FourSlashInterface { noMatchingBracePositionInCurrentFile(bracePosition: number): void; DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void; noDocCommentTemplate(): void; + codeFixAtPosition(expectedText: string, errorCode?: number): void; navigationBar(json: any): void; navigationItemsListCount(count: number, searchValue: string, matchKind?: string): void; From 6f4fb064ca27792b19828d842bd4472a45b4d7ae Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 5 Aug 2016 16:21:46 -0700 Subject: [PATCH 03/20] SuperFix fourslash tests --- tests/cases/fourslash/superFix1.ts | 10 ++++++++++ tests/cases/fourslash/superFix2.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/cases/fourslash/superFix1.ts create mode 100644 tests/cases/fourslash/superFix2.ts diff --git a/tests/cases/fourslash/superFix1.ts b/tests/cases/fourslash/superFix1.ts new file mode 100644 index 0000000000000..7fbe2cb4fd7bd --- /dev/null +++ b/tests/cases/fourslash/superFix1.ts @@ -0,0 +1,10 @@ +/// + +////class Base{ +////} +////class C extends Base{ +//// constructor() {[| |] +//// } +////} + +verify.codeFixAtPosition('super();'); diff --git a/tests/cases/fourslash/superFix2.ts b/tests/cases/fourslash/superFix2.ts new file mode 100644 index 0000000000000..880b5d43167c4 --- /dev/null +++ b/tests/cases/fourslash/superFix2.ts @@ -0,0 +1,13 @@ +/// + +////class Base{ +////} +////class C extends Base{ +//// private a:number; +//// constructor() {[| +//// this.a = 12; +//// super();|] +//// } +////} + +verify.codeFixAtPosition("super(); this.a = 12;"); \ No newline at end of file From e0b73c4a4a65f3c98a1b99ce5a981a1f89d44283 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 5 Aug 2016 16:44:43 -0700 Subject: [PATCH 04/20] Clean up --- src/services/codefixes/references.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/codefixes/references.ts b/src/services/codefixes/references.ts index 3675d626678e3..a9ab7a98b3395 100644 --- a/src/services/codefixes/references.ts +++ b/src/services/codefixes/references.ts @@ -1,6 +1,3 @@ /// /// /// -/// -/// -/// From 124e05fd68ffe662fca5bc5fa9ee91dd9b52fdcb Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 5 Aug 2016 16:44:43 -0700 Subject: [PATCH 05/20] PR Feedback --- src/compiler/core.ts | 24 +++++++++++++---------- src/compiler/diagnosticMessages.json | 14 ++++++------- src/compiler/types.ts | 1 - src/harness/fourslash.ts | 6 +++--- src/services/codefixes/codeFixProvider.ts | 7 ++----- src/services/codefixes/references.ts | 3 --- src/services/codefixes/superFixes.ts | 8 +++----- src/services/services.ts | 4 ++-- 8 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b07c21fa4cb6d..835a8be316750 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,4 +1,4 @@ -/// +/// /// @@ -115,15 +115,6 @@ namespace ts { return -1; } - export function firstOrUndefined(array: T[], predicate: (x: T) => boolean): T { - for (let i = 0, len = array.length; i < len; i++) { - if (predicate(array[i])) { - return array[i]; - } - } - return undefined; - } - export function indexOfAnyCharCode(text: string, charCodes: number[], start?: number): number { for (let i = start || 0, len = text.length; i < len; i++) { if (contains(charCodes, text.charCodeAt(i))) { @@ -229,6 +220,19 @@ namespace ts { return true; } + /** + * Returns the first element that matches the predicate, or undefined if none + * could be found. + */ + export function find(array: T[], predicate: (item: T) => boolean): T { + for (let i = 0, len = array.length; i < len; i++) { + if (predicate(array[i])) { + return array[i]; + } + } + return undefined; + } + /** * Returns the last element of an array if non-empty, undefined otherwise. */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 1dffc35c74c17..34f5d481075d1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3033,31 +3033,31 @@ "code": 17010 }, "Add missing 'super()' call.": { - "category": "CodeFix", + "category": "Message", "code": 90001 }, "Make 'super()' call the first statement in the constructor.": { - "category": "CodeFix", + "category": "Message", "code": 90002 }, "Change 'extends' to 'implements'": { - "category": "CodeFix", + "category": "Message", "code": 90003 }, "Remove unused identifiers": { - "category": "CodeFix", + "category": "Message", "code": 90004 }, "Implement interface on reference": { - "category": "CodeFix", + "category": "Message", "code": 90005 }, "Implement interface on class": { - "category": "CodeFix", + "category": "Message", "code": 90006 }, "Implement inherited abstract class": { - "category": "CodeFix", + "category": "Message", "code": 90007 } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e31a4b0ace188..28ededebb0de1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2547,7 +2547,6 @@ namespace ts { Warning, Error, Message, - CodeFix, } export enum ModuleResolutionKind { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 91597d7f5931e..19fa9ba5b4abe 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1,4 +1,4 @@ -// +// // Copyright (c) Microsoft Corporation. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -1879,11 +1879,11 @@ namespace FourSlash { this.raiseError("Errors expected."); } - if (diagnostics.length > 1 && !errorCode) { + if (diagnostics.length > 1 && errorCode !== undefined) { this.raiseError("When there's more than one error, you must specify the errror to fix."); } - const diagnostic = !errorCode ? diagnostics[0] : ts.firstOrUndefined(diagnostics, d => d.code == errorCode); + const diagnostic = !errorCode ? diagnostics[0] : ts.find(diagnostics, d => d.code == errorCode); const actual = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [`TS${diagnostic.code}`]); diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts index ca877aef46aca..71199af88fa2c 100644 --- a/src/services/codefixes/codeFixProvider.ts +++ b/src/services/codefixes/codeFixProvider.ts @@ -1,7 +1,6 @@ -/* @internal */ +/* @internal */ namespace ts { export interface CodeFix { - name: string; errorCodes: string[]; getCodeActions(context: CodeFixContext): CodeAction[]; } @@ -14,7 +13,7 @@ namespace ts { newLineCharacter: string; } - export namespace codeFix { + export namespace codefix { const codeFixes: Map = {}; export function registerCodeFix(action: CodeFix) { @@ -37,8 +36,6 @@ namespace ts { const fixes = codeFixes[context.errorCode]; let allActions: CodeAction[] = []; - Debug.assert(fixes && fixes.length > 0, "No fixes found for error: '${errorCode}'."); - forEach(fixes, f => { const actions = f.getCodeActions(context); if (actions && actions.length > 0) { diff --git a/src/services/codefixes/references.ts b/src/services/codefixes/references.ts index 3675d626678e3..a9ab7a98b3395 100644 --- a/src/services/codefixes/references.ts +++ b/src/services/codefixes/references.ts @@ -1,6 +1,3 @@ /// /// /// -/// -/// -/// diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index 2c6a67de513b2..d06ba9df955e1 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -1,13 +1,12 @@ /* @internal */ -namespace ts.codeFix { +namespace ts.codefix { function getOpenBraceEnd(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).getEnd(); } registerCodeFix({ - name: "AddMissingSuperCallFix", - errorCodes: ["TS2377"], + errorCodes: [`TS${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); @@ -22,8 +21,7 @@ namespace ts.codeFix { }); registerCodeFix({ - name: "MakeSuperCallTheFirstStatementInTheConstructor", - errorCodes: ["TS17009"], + errorCodes: [`TS${Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code}`], getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; diff --git a/src/services/services.ts b/src/services/services.ts index b189a6375b72f..839fc4c298e9f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1902,7 +1902,7 @@ namespace ts { } export function getSupportedCodeFixes() { - return codeFix.CodeFixProvider.getSupportedErrorCodes(); + return codefix.CodeFixProvider.getSupportedErrorCodes(); } // Cache host information about script Should be refreshed @@ -3041,7 +3041,7 @@ namespace ts { documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - const codeFixProvider: codeFix.CodeFixProvider = new codeFix.CodeFixProvider(); + const codeFixProvider: codefix.CodeFixProvider = new codefix.CodeFixProvider(); let ruleProvider: formatting.RulesProvider; let program: Program; let lastProjectVersion: string; From f931e606b18e3635d545b5d1f768b0b05b01a2ca Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 26 Aug 2016 15:33:23 -0700 Subject: [PATCH 06/20] Fix build break caused by merge from master --- src/compiler/core.ts | 13 ------------- src/services/codefixes/codeFixProvider.ts | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fdfa139a21c4e..c6effb658b7ca 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -292,19 +292,6 @@ namespace ts { return true; } - /** - * Returns the first element that matches the predicate, or undefined if none - * could be found. - */ - export function find(array: T[], predicate: (item: T) => boolean): T { - for (let i = 0, len = array.length; i < len; i++) { - if (predicate(array[i])) { - return array[i]; - } - } - return undefined; - } - /** * Returns the last element of an array if non-empty, undefined otherwise. */ diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts index 71199af88fa2c..9c4e7404c95a0 100644 --- a/src/services/codefixes/codeFixProvider.ts +++ b/src/services/codefixes/codeFixProvider.ts @@ -14,7 +14,7 @@ namespace ts { } export namespace codefix { - const codeFixes: Map = {}; + const codeFixes = createMap(); export function registerCodeFix(action: CodeFix) { forEach(action.errorCodes, error => { @@ -29,7 +29,7 @@ namespace ts { export class CodeFixProvider { public static getSupportedErrorCodes() { - return getKeys(codeFixes); + return Object.keys(codeFixes); } public getFixes(context: CodeFixContext): CodeAction[] { From 49b65c749fc291dfb22b997820ce0c369ad40c43 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 9 Sep 2016 14:30:28 -0700 Subject: [PATCH 07/20] PR feedback --- src/services/codefixes/superFixes.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index d06ba9df955e1..622ded06d31a3 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -10,7 +10,10 @@ namespace ts.codefix { getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; const token = getTokenAtPosition(sourceFile, context.span.start); - Debug.assert(token.kind === SyntaxKind.ConstructorKeyword, "Failed to find the constructor."); + + if (token.kind !== SyntaxKind.ConstructorKeyword) { + return undefined; + } const newPosition = getOpenBraceEnd(token.parent, sourceFile); return [{ @@ -26,14 +29,18 @@ namespace ts.codefix { const sourceFile = context.sourceFile; const token = getTokenAtPosition(sourceFile, context.span.start); - const constructor = getContainingFunction(token); - Debug.assert(constructor.kind === SyntaxKind.Constructor, "Failed to find the constructor."); + if (token.kind !== SyntaxKind.ConstructorKeyword) { + return undefined; + } + const constructor = getContainingFunction(token); const superCall = findSuperCall((constructor).body); - Debug.assert(!!superCall, "Failed to find super call."); + if (!superCall) { + return undefined; + } const newPosition = getOpenBraceEnd(constructor, sourceFile); - const changes = [{ + const changes = [{ fileName: sourceFile.fileName, textChanges: [{ newText: superCall.getText(sourceFile), span: { start: newPosition, length: 0 } From 682f81257d345f72e230a53aa864d3dee4af6bdc Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 16 Sep 2016 14:52:33 -0700 Subject: [PATCH 08/20] PR feedback --- src/services/codefixes/codeFixProvider.ts | 32 +++++++++++------------ src/services/services.ts | 8 +++--- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts index 9c4e7404c95a0..55aa8d38f7a34 100644 --- a/src/services/codefixes/codeFixProvider.ts +++ b/src/services/codefixes/codeFixProvider.ts @@ -9,7 +9,7 @@ namespace ts { errorCode: string; sourceFile: SourceFile; span: TextSpan; - checker: TypeChecker; + program: Program; newLineCharacter: string; } @@ -27,24 +27,22 @@ namespace ts { }); } - export class CodeFixProvider { - public static getSupportedErrorCodes() { - return Object.keys(codeFixes); - } + export function getSupportedErrorCodes() { + return Object.keys(codeFixes); + } - public getFixes(context: CodeFixContext): CodeAction[] { - const fixes = codeFixes[context.errorCode]; - let allActions: CodeAction[] = []; + export function getFixes(context: CodeFixContext): CodeAction[] { + const fixes = codeFixes[context.errorCode]; + let allActions: CodeAction[] = []; - forEach(fixes, f => { - const actions = f.getCodeActions(context); - if (actions && actions.length > 0) { - allActions = allActions.concat(actions); - } - }); + forEach(fixes, f => { + const actions = f.getCodeActions(context); + if (actions && actions.length > 0) { + allActions = allActions.concat(actions); + } + }); - return allActions; - } + return allActions; } } -} \ No newline at end of file +} diff --git a/src/services/services.ts b/src/services/services.ts index b481ffa526969..bf6ea6278996d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -695,7 +695,7 @@ namespace ts { } export function getSupportedCodeFixes() { - return codefix.CodeFixProvider.getSupportedErrorCodes(); + return codefix.getSupportedErrorCodes(); } // Cache host information about script Should be refreshed @@ -918,7 +918,6 @@ namespace ts { documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - const codeFixProvider: codefix.CodeFixProvider = new codefix.CodeFixProvider(); let ruleProvider: formatting.RulesProvider; let program: Program; let lastProjectVersion: string; @@ -1588,7 +1587,6 @@ namespace ts { function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const checker = program.getTypeChecker(); let allFixes: CodeAction[] = []; forEach(errorCodes, error => { @@ -1596,11 +1594,11 @@ namespace ts { errorCode: error, sourceFile: sourceFile, span: { start, length: end - start }, - checker: checker, + program: program, newLineCharacter: getNewLineOrDefaultFromHost(host) }; - const fixes = codeFixProvider.getFixes(context); + const fixes = codefix.getFixes(context); if (fixes) { allFixes = allFixes.concat(fixes); } From 4f404ad92be1e6c318eef04cecf4026ec4c39df2 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Tue, 4 Oct 2016 16:58:17 -0700 Subject: [PATCH 09/20] Implement codefixes in tsserver --- src/server/protocol.d.ts | 96 +++++++++++++++++++++++++++++++++++++++- src/server/session.ts | 52 +++++++++++++++++++++- src/server/utilities.ts | 3 +- 3 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index f0dfe4eb1302c..e8919f843ebca 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -203,6 +203,53 @@ declare namespace ts.server.protocol { position?: number; } + /** + * Request for the available codefixed at a specific position. + */ + export interface CodeFixRequest extends Request { + arguments: CodeFixRequestArgs; + } + + /** + * Instances of this interface specify errorcodes on a specific location in a sourcefile. + */ + export interface CodeFixRequestArgs extends FileRequestArgs { + /** + * The line number for the request (1-based). + */ + startLine?: number; + + /** + * The character offset (on the line) for the request (1-based). + */ + startOffset?: number; + + /** + * Position (can be specified instead of line/offset pair) + */ + startPosition?: number; + + /** + * The line number for the request (1-based). + */ + endLine?: number; + + /** + * The character offset (on the line) for the request (1-based). + */ + endOffset?: number; + + /** + * Position (can be specified instead of line/offset pair) + */ + endPosition?: number; + + /** + * Errorcodes we want to get the fixes for. + */ + errorCodes?: string[] + } + /** * A request whose arguments specify a file location (file, line, col). */ @@ -428,7 +475,6 @@ declare namespace ts.server.protocol { findInStrings?: boolean; } - /** * Rename request; value of command field is "rename". Return * response giving the file locations that reference the symbol @@ -873,6 +919,18 @@ declare namespace ts.server.protocol { newText: string; } + export interface FileCodeEdits { + fileName: string; + textChanges: CodeEdit[]; + } + + export interface CodeActionResponse extends Response { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileCodeEdits[]; + } + /** * Format and format on key response message. */ @@ -1518,4 +1576,40 @@ declare namespace ts.server.protocol { export interface NavBarResponse extends Response { body?: NavigationBarItem[]; } + + export interface CodeAction { + /** + * Description of the code action to display in the UI of the editor. + */ + description: string; + + /** + * Changes to apply to each file as part of the code action. + */ + changes: FileTextChanges[] + } + + export interface FileTextChanges { + /** + * File to apply the change to. + */ + fileName: string; + + /** + * Changes to apply to the file. + */ + textChanges: TextChange[]; + } + + export class TextChange { + /** + * The span for the text change. + */ + span: TextSpan; + + /** + * New text for the span, can be an empty string if we want to delete text. + */ + newText: string; + } } diff --git a/src/server/session.ts b/src/server/session.ts index d076d5deb6d97..ff4384aafbd58 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -134,6 +134,8 @@ namespace ts.server { export const NameOrDottedNameSpan = "nameOrDottedNameSpan"; export const BreakpointStatement = "breakpointStatement"; export const CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects"; + export const GetCodeFixes = "getCodeFixes"; + export const GetCodeFixesFull = "getCodeFixes-full"; } export function formatMessage(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string { @@ -751,7 +753,7 @@ namespace ts.server { return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ false, errorOnMissingProject); } - private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) { + private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) { const file = toNormalizedPath(uncheckedFileName); const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, refreshInferredProjects); if (!project && errorOnMissingProject) { @@ -1199,6 +1201,48 @@ namespace ts.server { } } + private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] { + const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + + const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const startPosition = getStartPosition(); + const endPosition = getEndPosition(); + + const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes); + if (!codeActions) { + return undefined; + } + if (simplifiedResult) { + return codeActions.map(mapCodeAction); + } else { + return codeActions; + } + + function mapCodeAction(source: CodeAction): protocol.CodeAction { + return { + description: source.description, + changes: source.changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => ({ + span: { + start: scriptInfo.positionToLineOffset(textChange.span.start), + end: scriptInfo.positionToLineOffset(textChange.span.start + textChange.span.length) + }, + newText: textChange.newText + })) + })) + }; + } + + function getStartPosition() { + return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset); + } + + function getEndPosition() { + return args.endPosition !== undefined ? args.endPosition : scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); + } + } + private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); @@ -1521,6 +1565,12 @@ namespace ts.server { [CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => { this.projectService.reloadProjects(); return this.notRequired(); + }, + [CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => { + return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => { + return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false)); } }); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 3f99da9977ea0..beceab79229c0 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -201,7 +201,8 @@ namespace ts.server { dispose: (): any => throwLanguageServiceIsDisabledError(), getCompletionEntrySymbol: (): any => throwLanguageServiceIsDisabledError(), getImplementationAtPosition: (): any => throwLanguageServiceIsDisabledError(), - getSourceFile: (): any => throwLanguageServiceIsDisabledError() + getSourceFile: (): any => throwLanguageServiceIsDisabledError(), + getCodeFixesAtPosition: (): any => throwLanguageServiceIsDisabledError() }; export interface ServerLanguageServiceHost { From 1cc973273f3a504037bfadf086edef170eeff214 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Tue, 4 Oct 2016 17:37:46 -0700 Subject: [PATCH 10/20] PR feedback --- src/services/codefixes/fixes.ts | 1 + src/services/codefixes/references.ts | 3 --- src/services/services.ts | 10 +++++++--- src/services/tsconfig.json | 3 +-- 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 src/services/codefixes/fixes.ts delete mode 100644 src/services/codefixes/references.ts diff --git a/src/services/codefixes/fixes.ts b/src/services/codefixes/fixes.ts new file mode 100644 index 0000000000000..d64a99ca1b9e6 --- /dev/null +++ b/src/services/codefixes/fixes.ts @@ -0,0 +1 @@ +/// diff --git a/src/services/codefixes/references.ts b/src/services/codefixes/references.ts deleted file mode 100644 index a9ab7a98b3395..0000000000000 --- a/src/services/codefixes/references.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// -/// -/// diff --git a/src/services/services.ts b/src/services/services.ts index bed1869b7fb83..050096b45d45b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -24,7 +24,8 @@ /// /// /// -/// +/// +/// namespace ts { /** The version of the language service API */ @@ -1641,15 +1642,18 @@ namespace ts { function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); + const span = { start, length: end - start }; + const newLineChar = getNewLineOrDefaultFromHost(host); + let allFixes: CodeAction[] = []; forEach(errorCodes, error => { const context = { errorCode: error, sourceFile: sourceFile, - span: { start, length: end - start }, + span: span, program: program, - newLineCharacter: getNewLineOrDefaultFromHost(host) + newLineCharacter: newLineChar }; const fixes = codefix.getFixes(context); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 0f3059a449df2..0066ef94343d1 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -80,7 +80,6 @@ "formatting/smartIndenter.ts", "formatting/tokenRange.ts", "codeFixes/codeFixProvider.ts", - "codeFixes/references.ts", - "codeFixes/superFixes.ts" + "codeFixes/fixes.ts" ] } From ebcfce41277bf80cf57f5a0d2ba9b081109ddb14 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Wed, 5 Oct 2016 11:29:38 -0700 Subject: [PATCH 11/20] Error span moved from constructor to this keyword. --- src/services/codefixes/superFixes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index 622ded06d31a3..c61e4281e8fb8 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -29,7 +29,7 @@ namespace ts.codefix { const sourceFile = context.sourceFile; const token = getTokenAtPosition(sourceFile, context.span.start); - if (token.kind !== SyntaxKind.ConstructorKeyword) { + if (token.kind !== SyntaxKind.ThisKeyword) { return undefined; } From 163e758e1060616f6fd4a4ebb12ec1a02acd559c Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Thu, 6 Oct 2016 11:44:53 -0700 Subject: [PATCH 12/20] Handle case where this access is inside the supercall --- src/harness/fourslash.ts | 35 +++++++++++++++++++++------- src/services/codefixes/superFixes.ts | 13 ++++++++++- tests/cases/fourslash/fourslash.ts | 1 + tests/cases/fourslash/superFix3.ts | 12 ++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/superFix3.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 7192cf180e1e2..5fc2674404f1a 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2041,13 +2041,7 @@ namespace FourSlash { } } - public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) { - - const ranges = this.getRanges(); - if (ranges.length == 0) { - this.raiseError("At least one range should be specified in the testfile."); - } - + private getCodeFixes(errorCode?: number) { const fileName = this.activeFile.fileName; const diagnostics = this.getDiagnostics(fileName); @@ -2061,7 +2055,16 @@ namespace FourSlash { const diagnostic = !errorCode ? diagnostics[0] : ts.find(diagnostics, d => d.code == errorCode); - const actual = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [`TS${diagnostic.code}`]); + return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [`TS${diagnostic.code}`]); + } + + public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) { + const ranges = this.getRanges(); + if (ranges.length == 0) { + this.raiseError("At least one range should be specified in the testfile."); + } + + const actual = this.getCodeFixes(errorCode); if (!actual || actual.length == 0) { this.raiseError("No codefixes returned."); @@ -2350,6 +2353,18 @@ namespace FourSlash { } } + public verifyCodeFixAvailable(negative: boolean, errorCode?:number ) { + const fixes = this.getCodeFixes(errorCode); + + if (negative && fixes && fixes.length > 0) { + this.raiseError(`verifyCodeFixAvailable failed - expected no fixes, actual: ${fixes.length}`); + } + + if (!negative && (fixes === undefined || fixes.length === 0)) { + this.raiseError(`verifyCodeFixAvailable failed - expected code fixes, actual: 0`); + } + } + // Get the text of the entire line the caret is currently at private getCurrentLineContent() { const text = this.getFileContent(this.activeFile.fileName); @@ -3137,6 +3152,10 @@ namespace FourSlashInterface { public isValidBraceCompletionAtPosition(openingBrace: string) { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } + + public codeFixAvailable(errorCode?: number) { + this.state.verifyCodeFixAvailable(this.negative, errorCode); + } } export class Verify extends VerifyNegatable { diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index c61e4281e8fb8..2f0a78580cba8 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -34,11 +34,22 @@ namespace ts.codefix { } const constructor = getContainingFunction(token); - const superCall = findSuperCall((constructor).body); + const superCall = findSuperCall((constructor).body); if (!superCall) { return undefined; } + // figure out if the this access is actuall 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 arguments = (superCall.expression).arguments; + for (let i = 0; i < arguments.length; i++){ + if ((arguments[i]).expression === token) { + return undefined; + } + } + } + const newPosition = getOpenBraceEnd(constructor, sourceFile); const changes = [{ fileName: sourceFile.fileName, textChanges: [{ diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 042f6d563a5a7..5e8666e8670c5 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -136,6 +136,7 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; + codeFixAvailable(): void; } class verify extends verifyNegatable { assertHasRanges(ranges: Range[]): void; diff --git a/tests/cases/fourslash/superFix3.ts b/tests/cases/fourslash/superFix3.ts new file mode 100644 index 0000000000000..9b443b7df62f6 --- /dev/null +++ b/tests/cases/fourslash/superFix3.ts @@ -0,0 +1,12 @@ +/// + +////class Base{ +//// constructor(id: number) { } +////} +////class C extends Base{ +//// constructor(private a:number) { +//// super(this.a); +//// } +////} + +verify.not.codeFixAvailable(); \ No newline at end of file From 75e1b80ad59d54af5029cdc61ec14a756a0b0063 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Thu, 6 Oct 2016 13:21:58 -0700 Subject: [PATCH 13/20] Use just the errorcode, without the TS prefix --- src/harness/fourslash.ts | 2 +- src/harness/harnessLanguageService.ts | 2 +- src/server/client.ts | 2 +- src/server/protocol.d.ts | 2 +- src/services/codefixes/codeFixProvider.ts | 6 +++--- src/services/codefixes/superFixes.ts | 8 ++++---- src/services/services.ts | 2 +- src/services/shims.ts | 2 +- src/services/types.ts | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5fc2674404f1a..cadda289daa57 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2055,7 +2055,7 @@ namespace FourSlash { const diagnostic = !errorCode ? diagnostics[0] : ts.find(diagnostics, d => d.code == errorCode); - return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [`TS${diagnostic.code}`]); + return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code]); } public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 514049511fc80..6af168796f9c9 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -486,7 +486,7 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): ts.CodeAction[] { + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): ts.CodeAction[] { return unwrapJSONCallResult(this.shim.getCodeFixesAtPosition(fileName, start, end, JSON.stringify(errorCodes))); } getEmitOutput(fileName: string): ts.EmitOutput { diff --git a/src/server/client.ts b/src/server/client.ts index f9fdf62423244..089a0eed12cfa 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -630,7 +630,7 @@ namespace ts.server { throw new Error("Not Implemented Yet."); } - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): ts.CodeAction[] { + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): ts.CodeAction[] { throw new Error("Not Implemented Yet."); } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index e8919f843ebca..02e488650cdd2 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -247,7 +247,7 @@ declare namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: string[] + errorCodes?: number[] } /** diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts index 55aa8d38f7a34..bc5ee1fb97ed3 100644 --- a/src/services/codefixes/codeFixProvider.ts +++ b/src/services/codefixes/codeFixProvider.ts @@ -1,12 +1,12 @@ -/* @internal */ +/* @internal */ namespace ts { export interface CodeFix { - errorCodes: string[]; + errorCodes: number[]; getCodeActions(context: CodeFixContext): CodeAction[]; } export interface CodeFixContext { - errorCode: string; + errorCode: number; sourceFile: SourceFile; span: TextSpan; program: Program; diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index 2f0a78580cba8..35462f0d49eec 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -1,4 +1,4 @@ -/* @internal */ +/* @internal */ namespace ts.codefix { function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) { // First token is the open curly, this is where we want to put the 'super' call. @@ -6,7 +6,7 @@ namespace ts.codefix { } registerCodeFix({ - errorCodes: [`TS${Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code}`], + 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); @@ -24,7 +24,7 @@ namespace ts.codefix { }); registerCodeFix({ - errorCodes: [`TS${Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code}`], + errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code], getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; @@ -43,7 +43,7 @@ namespace ts.codefix { // i.e. super(this.a), since in that case we won't suggest a fix if (superCall.expression && superCall.expression.kind == SyntaxKind.CallExpression) { const arguments = (superCall.expression).arguments; - for (let i = 0; i < arguments.length; i++){ + for (let i = 0; i < arguments.length; i++) { if ((arguments[i]).expression === token) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index 050096b45d45b..7f81649bacb0b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1639,7 +1639,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = { start, length: end - start }; diff --git a/src/services/shims.ts b/src/services/shims.ts index 63f766eff5aba..19dadba89bf40 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -961,7 +961,7 @@ namespace ts { return this.forwardJSONCall( `getCodeFixesAtPosition( '${fileName}', ${start}, ${end}, ${errorCodes}')`, () => { - const localErrors: string[] = JSON.parse(errorCodes); + const localErrors: number[] = JSON.parse(errorCodes); return this.languageService.getCodeFixesAtPosition(fileName, start, end, localErrors); } ); diff --git a/src/services/types.ts b/src/services/types.ts index f8b57ab0d32f8..59abe2f799899 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -239,7 +239,7 @@ namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string[]): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; From 1e9b6536cfce4d451e8c721d35661f18e28809c5 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Thu, 6 Oct 2016 13:59:54 -0700 Subject: [PATCH 14/20] Fix linting issues --- src/harness/fourslash.ts | 2 +- src/server/protocol.d.ts | 4 ++-- src/server/session.ts | 3 ++- src/services/types.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cadda289daa57..f54804c0eb5d4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2353,7 +2353,7 @@ namespace FourSlash { } } - public verifyCodeFixAvailable(negative: boolean, errorCode?:number ) { + public verifyCodeFixAvailable(negative: boolean, errorCode?: number) { const fixes = this.getCodeFixes(errorCode); if (negative && fixes && fixes.length > 0) { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 02e488650cdd2..c3f5bb97fc5c8 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -247,7 +247,7 @@ declare namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[] + errorCodes?: number[]; } /** @@ -1586,7 +1586,7 @@ declare namespace ts.server.protocol { /** * Changes to apply to each file as part of the code action. */ - changes: FileTextChanges[] + changes: FileTextChanges[]; } export interface FileTextChanges { diff --git a/src/server/session.ts b/src/server/session.ts index ff4384aafbd58..7f62e688907fe 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1214,7 +1214,8 @@ namespace ts.server { } if (simplifiedResult) { return codeActions.map(mapCodeAction); - } else { + } + else { return codeActions; } diff --git a/src/services/types.ts b/src/services/types.ts index 59abe2f799899..318166604f5c6 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -297,7 +297,7 @@ namespace ts { fileName: string; textChanges: TextChange[]; } - + export interface CodeAction { /** Description of the code action to display in the UI of the editor */ description: string; From c29e9b7e4b984370a35a949b97280dafb56e1141 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 7 Oct 2016 10:31:12 -0700 Subject: [PATCH 15/20] PR feedback --- src/harness/harnessLanguageService.ts | 2 +- src/server/protocol.d.ts | 35 ++++----------- src/server/session.ts | 52 ++++++++++++----------- src/services/codefixes/codeFixProvider.ts | 2 +- src/services/services.ts | 2 + src/services/shims.ts | 19 --------- 6 files changed, 40 insertions(+), 72 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6af168796f9c9..99cee7dbd41f6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -487,7 +487,7 @@ namespace Harness.LanguageService { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): ts.CodeAction[] { - return unwrapJSONCallResult(this.shim.getCodeFixesAtPosition(fileName, start, end, JSON.stringify(errorCodes))); + throw new Error("Not supported on the shim."); } getEmitOutput(fileName: string): ts.EmitOutput { return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index c3f5bb97fc5c8..bb95144af79ae 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -204,7 +204,7 @@ declare namespace ts.server.protocol { } /** - * Request for the available codefixed at a specific position. + * Request for the available codefixes at a specific position. */ export interface CodeFixRequest extends Request { arguments: CodeFixRequestArgs; @@ -924,7 +924,12 @@ declare namespace ts.server.protocol { textChanges: CodeEdit[]; } - export interface CodeActionResponse extends Response { + export interface CodeFixResponse extends Response { + /** The code actions that are available */ + codeActions: CodeAction[]; + } + + export interface CodeAction { /** Description of the code action to display in the UI of the editor */ description: string; /** Text changes to apply to each file as part of the code action */ @@ -1586,30 +1591,6 @@ declare namespace ts.server.protocol { /** * Changes to apply to each file as part of the code action. */ - changes: FileTextChanges[]; - } - - export interface FileTextChanges { - /** - * File to apply the change to. - */ - fileName: string; - - /** - * Changes to apply to the file. - */ - textChanges: TextChange[]; - } - - export class TextChange { - /** - * The span for the text change. - */ - span: TextSpan; - - /** - * New text for the span, can be an empty string if we want to delete text. - */ - newText: string; + changes: FileCodeEdits[]; } } diff --git a/src/server/session.ts b/src/server/session.ts index 7f62e688907fe..ccbf6caea1f2a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -136,6 +136,7 @@ namespace ts.server { export const CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects"; export const GetCodeFixes = "getCodeFixes"; export const GetCodeFixesFull = "getCodeFixes-full"; + export const GetSupportedCodeFixes = "getSupportedCodeFixes"; } export function formatMessage(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string { @@ -844,13 +845,7 @@ namespace ts.server { return undefined; } - return edits.map((edit) => { - return { - start: scriptInfo.positionToLineOffset(edit.span.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(edit.span)), - newText: edit.newText ? edit.newText : "" - }; - }); + return edits.map(edit => this.convertTextChangeToCodeEdit(edit, scriptInfo)); } private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) { @@ -1201,6 +1196,10 @@ namespace ts.server { } } + private getSupportedCodeFixes(): string[] { + return ts.getSupportedCodeFixes(); + } + private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); @@ -1213,28 +1212,12 @@ namespace ts.server { return undefined; } if (simplifiedResult) { - return codeActions.map(mapCodeAction); + return codeActions.map(codeAction => this.mapCodeAction(codeAction, scriptInfo)); } else { return codeActions; } - function mapCodeAction(source: CodeAction): protocol.CodeAction { - return { - description: source.description, - changes: source.changes.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => ({ - span: { - start: scriptInfo.positionToLineOffset(textChange.span.start), - end: scriptInfo.positionToLineOffset(textChange.span.start + textChange.span.length) - }, - newText: textChange.newText - })) - })) - }; - } - function getStartPosition() { return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset); } @@ -1244,6 +1227,24 @@ namespace ts.server { } } + private mapCodeAction(codeAction: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction { + return { + description: codeAction.description, + changes: codeAction.changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) + })) + }; + } + + private convertTextChangeToCodeEdit(change: ts.TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { + return { + start: scriptInfo.positionToLineOffset(change.span.start), + end: scriptInfo.positionToLineOffset(change.span.start + change.span.length), + newText: change.newText + }; + } + private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); @@ -1572,6 +1573,9 @@ namespace ts.server { }, [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => { return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false)); + }, + [CommandNames.GetSupportedCodeFixes]: (request: protocol.Request) => { + return this.requiredResponse(this.getSupportedCodeFixes()); } }); diff --git a/src/services/codefixes/codeFixProvider.ts b/src/services/codefixes/codeFixProvider.ts index bc5ee1fb97ed3..c61cbe1b19ea5 100644 --- a/src/services/codefixes/codeFixProvider.ts +++ b/src/services/codefixes/codeFixProvider.ts @@ -2,7 +2,7 @@ namespace ts { export interface CodeFix { errorCodes: number[]; - getCodeActions(context: CodeFixContext): CodeAction[]; + getCodeActions(context: CodeFixContext): CodeAction[] | undefined; } export interface CodeFixContext { diff --git a/src/services/services.ts b/src/services/services.ts index 7f81649bacb0b..4aba7c4f88f60 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1648,6 +1648,8 @@ namespace ts { let allFixes: CodeAction[] = []; forEach(errorCodes, error => { + cancellationToken.throwIfCancellationRequested(); + const context = { errorCode: error, sourceFile: sourceFile, diff --git a/src/services/shims.ts b/src/services/shims.ts index 19dadba89bf40..56ab852606cd7 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -251,8 +251,6 @@ namespace ts { */ isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string): string; - getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -268,7 +266,6 @@ namespace ts { getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; discoverTypings(discoverTypingsJson: string): string; - getSupportedCodeFixes(): string; } function logInternalError(logger: Logger, err: Error) { @@ -957,16 +954,6 @@ namespace ts { ); } - public getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: string): string { - return this.forwardJSONCall( - `getCodeFixesAtPosition( '${fileName}', ${start}, ${end}, ${errorCodes}')`, - () => { - const localErrors: number[] = JSON.parse(errorCodes); - return this.languageService.getCodeFixesAtPosition(fileName, start, end, localErrors); - } - ); - } - /// NAVIGATE TO /** Return a list of symbols that are interesting to navigate to */ @@ -1175,12 +1162,6 @@ namespace ts { info.compilerOptions); }); } - - public getSupportedCodeFixes(): string { - return this.forwardJSONCall("getSupportedCodeFixes()", - () => getSupportedCodeFixes() - ); - } } export class TypeScriptServicesFactory implements ShimFactory { From 77610f6e3eedbd423b57dd1a305544877287a7a2 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 7 Oct 2016 11:18:01 -0700 Subject: [PATCH 16/20] rename of the response body --- src/server/protocol.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index bb95144af79ae..66cbf56fc3a9e 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -926,7 +926,7 @@ declare namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - codeActions: CodeAction[]; + body?: CodeAction[]; } export interface CodeAction { From a83a2b0de03e1c8e28f2f2f1b2bbef1a8a6de83c Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 7 Oct 2016 13:58:53 -0700 Subject: [PATCH 17/20] Codefixes in client for testing the server. --- src/server/client.ts | 70 +++++++++++++++++++++-- src/server/protocol.d.ts | 5 ++ src/server/session.ts | 7 ++- tests/cases/fourslash/server/superFix1.ts | 10 ++++ 4 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/server/superFix1.ts diff --git a/src/server/client.ts b/src/server/client.ts index 089a0eed12cfa..20bddf7789519 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -425,11 +425,35 @@ namespace ts.server { } getSyntacticDiagnostics(fileName: string): Diagnostic[] { - throw new Error("Not Implemented Yet."); + const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName }; + + const request = this.processRequest(CommandNames.SyntacticDiagnosticsSync, args); + const response = this.processResponse(request); + + return (response.body).map(entry => this.convertDiagnostic(entry, fileName)); } getSemanticDiagnostics(fileName: string): Diagnostic[] { - throw new Error("Not Implemented Yet."); + const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName }; + + const request = this.processRequest(CommandNames.SemanticDiagnosticsSync, args); + const response = this.processResponse(request); + + return (response.body).map(entry => this.convertDiagnostic(entry, fileName)); + } + + convertDiagnostic(entry: protocol.Diagnostic, fileName: string): Diagnostic { + const start = this.lineOffsetToPosition(fileName, entry.start); + const end = this.lineOffsetToPosition(fileName, entry.end); + + return { + file: undefined, + start: start, + length: end - start, + messageText: entry.text, + category: undefined, + code: entry.code + }; } getCompilerOptionsDiagnostics(): Diagnostic[] { @@ -630,8 +654,46 @@ namespace ts.server { throw new Error("Not Implemented Yet."); } - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): ts.CodeAction[] { - throw new Error("Not Implemented Yet."); + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] { + const startLineOffset = this.positionToOneBasedLineOffset(fileName, start); + const endLineOffset = this.positionToOneBasedLineOffset(fileName, end); + + const args: protocol.CodeFixRequestArgs = { + file: fileName, + startLine: startLineOffset.line, + startOffset: startLineOffset.offset, + endLine: endLineOffset.line, + endOffset: endLineOffset.offset, + errorCodes: errorCodes, + }; + + const request = this.processRequest(CommandNames.GetCodeFixesFull, args); + const response = this.processResponse(request); + + return response.body.map(entry => this.convertCodeActions(entry, fileName)); + } + + 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)) + })) + }; + } + + convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange { + const start = this.lineOffsetToPosition(fileName, change.start); + const end = this.lineOffsetToPosition(fileName, change.end); + + return { + span: { + start: start, + length: end - start + }, + newText: change.newText ? change.newText : "" + }; } getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 66cbf56fc3a9e..60fd61c05cfb7 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -1310,6 +1310,11 @@ declare namespace ts.server.protocol { * Text of diagnostic message. */ text: string; + + /** + * The error code of the diagnostic message. + */ + code?: number; } export interface DiagnosticEventBody { diff --git a/src/server/session.ts b/src/server/session.ts index ccbf6caea1f2a..c945dca99a4ab 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1,4 +1,4 @@ -/// +/// /// /// /// @@ -44,7 +44,8 @@ namespace ts.server { return { start: scriptInfo.positionToLineOffset(diag.start), end: scriptInfo.positionToLineOffset(diag.start + diag.length), - text: ts.flattenDiagnosticMessageText(diag.messageText, "\n") + text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), + code: diag.code }; } @@ -1241,7 +1242,7 @@ namespace ts.server { return { start: scriptInfo.positionToLineOffset(change.span.start), end: scriptInfo.positionToLineOffset(change.span.start + change.span.length), - newText: change.newText + newText: change.newText ? change.newText : "" }; } diff --git a/tests/cases/fourslash/server/superFix1.ts b/tests/cases/fourslash/server/superFix1.ts new file mode 100644 index 0000000000000..7fbe2cb4fd7bd --- /dev/null +++ b/tests/cases/fourslash/server/superFix1.ts @@ -0,0 +1,10 @@ +/// + +////class Base{ +////} +////class C extends Base{ +//// constructor() {[| |] +//// } +////} + +verify.codeFixAtPosition('super();'); From f3103100e307ebaa5d34ad4293daab9062fc31d9 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 7 Oct 2016 14:13:32 -0700 Subject: [PATCH 18/20] Deal with buildbreak --- src/services/codefixes/superFixes.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/superFixes.ts b/src/services/codefixes/superFixes.ts index 35462f0d49eec..f117799f3e1c2 100644 --- a/src/services/codefixes/superFixes.ts +++ b/src/services/codefixes/superFixes.ts @@ -34,7 +34,7 @@ namespace ts.codefix { } const constructor = getContainingFunction(token); - const superCall = findSuperCall((constructor).body); + const superCall = findSuperCall((constructor).body); if (!superCall) { return undefined; } @@ -67,9 +67,9 @@ namespace ts.codefix { changes }]; - function findSuperCall(n: Node): Node { - if (n.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((n).expression)) { - return n; + function findSuperCall(n: Node): ExpressionStatement { + if (n.kind === SyntaxKind.ExpressionStatement && isSuperCall((n).expression)) { + return n; } if (isFunctionLike(n)) { return undefined; From 7c96c288635012a869bb8c620ff2d9e3d813f6a4 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Fri, 7 Oct 2016 15:55:33 -0700 Subject: [PATCH 19/20] Fix test failure --- src/server/client.ts | 2 +- tests/cases/fourslash/server/{superFix1.ts => codefix.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/cases/fourslash/server/{superFix1.ts => codefix.ts} (100%) diff --git a/src/server/client.ts b/src/server/client.ts index 20bddf7789519..2acc0a58cadd6 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -667,7 +667,7 @@ namespace ts.server { errorCodes: errorCodes, }; - const request = this.processRequest(CommandNames.GetCodeFixesFull, args); + const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); return response.body.map(entry => this.convertCodeActions(entry, fileName)); diff --git a/tests/cases/fourslash/server/superFix1.ts b/tests/cases/fourslash/server/codefix.ts similarity index 100% rename from tests/cases/fourslash/server/superFix1.ts rename to tests/cases/fourslash/server/codefix.ts From dc516c050ab07f4b8a10355595ead436bbb568e5 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Tue, 11 Oct 2016 17:30:49 -0700 Subject: [PATCH 20/20] Remove duplicate interface --- src/server/protocol.d.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 3ef02d4e49310..22799ee4759f0 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -1,4 +1,4 @@ -/** +/** * Declaration module describing the TypeScript Server protocol */ declare namespace ts.server.protocol { @@ -1592,16 +1592,4 @@ declare namespace ts.server.protocol { export interface NavBarResponse extends Response { body?: NavigationBarItem[]; } - - export interface CodeAction { - /** - * Description of the code action to display in the UI of the editor. - */ - description: string; - - /** - * Changes to apply to each file as part of the code action. - */ - changes: FileCodeEdits[]; - } }