Skip to content

Pvb/codeaction/api #10185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Oct 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
94ea825
Api Changes and simple superfixes
Aug 5, 2016
466d26f
Fourslash support
Aug 5, 2016
6f4fb06
SuperFix fourslash tests
Aug 5, 2016
e0b73c4
Clean up
Aug 5, 2016
124e05f
PR Feedback
Aug 5, 2016
730f31f
Merge branch 'master' into pvb/codeaction/api
Aug 26, 2016
9a26da1
Merge branch 'pvb/codeaction/api' of https://github.com/Microsoft/Typ…
Aug 26, 2016
f931e60
Fix build break caused by merge from master
Aug 26, 2016
66e1c92
Merge branch 'master' into pvb/codeaction/api
Aug 31, 2016
49b65c7
PR feedback
Sep 9, 2016
3df79d5
Merge branch 'master' into pvb/codeaction/api
Sep 16, 2016
682f812
PR feedback
Sep 16, 2016
6e8eb1d
Merge branch 'pvb/codeaction/api' of https://github.com/Microsoft/Typ…
Oct 4, 2016
20dea29
Merge branch 'master' into pvb/codeaction/api
Oct 4, 2016
4f404ad
Implement codefixes in tsserver
Oct 4, 2016
1cc9732
PR feedback
Oct 5, 2016
cf4e300
Merge branch 'master' into pvb/codeaction/api
Oct 5, 2016
ebcfce4
Error span moved from constructor to this keyword.
Oct 5, 2016
163e758
Handle case where this access is inside the supercall
Oct 6, 2016
75e1b80
Use just the errorcode, without the TS prefix
Oct 6, 2016
1e9b653
Fix linting issues
Oct 6, 2016
c29e9b7
PR feedback
Oct 7, 2016
77610f6
rename of the response body
Oct 7, 2016
a83a2b0
Codefixes in client for testing the server.
Oct 7, 2016
a88ceb5
Merge branch 'master' into pvb/codeaction/api
Oct 7, 2016
f310310
Deal with buildbreak
Oct 7, 2016
7c96c28
Fix test failure
Oct 7, 2016
dc516c0
Remove duplicate interface
Oct 12, 2016
9b98d00
Merge branch 'master' into pvb/codeaction/api
Oct 12, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3073,13 +3073,40 @@
"category": "Error",
"code": 17010
},

"Circularity detected while resolving configuration: {0}": {
"category": "Error",
"code": 18000
},
"The path in an 'extends' options must be relative or rooted.": {
"category": "Error",
"code": 18001
},
"Add missing 'super()' call.": {
"category": "Message",
"code": 90001
},
"Make 'super()' call the first statement in the constructor.": {
"category": "Message",
"code": 90002
},
"Change 'extends' to 'implements'": {
"category": "Message",
"code": 90003
},
"Remove unused identifiers": {
"category": "Message",
"code": 90004
},
"Implement interface on reference": {
"category": "Message",
"code": 90005
},
"Implement interface on class": {
"category": "Message",
"code": 90006
},
"Implement inherited abstract class": {
"category": "Message",
"code": 90007
}
}
72 changes: 68 additions & 4 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -427,7 +427,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}'`);
}
}

Expand Down Expand Up @@ -742,7 +742,6 @@ namespace FourSlash {
}
}


public verifyCompletionListAllowsNewIdentifier(negative: boolean) {
const completions = this.getCompletionListAtCaret();

Expand Down Expand Up @@ -1611,7 +1610,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");
}
}
Expand Down Expand Up @@ -1677,6 +1676,10 @@ namespace FourSlash {
}
}

private removeWhitespace(text: string): string {
return text.replace(/\s/g, "");
}

public goToBOF() {
this.goToPosition(0);
}
Expand Down Expand Up @@ -2038,6 +2041,47 @@ namespace FourSlash {
}
}

private getCodeFixes(errorCode?: number) {
const fileName = this.activeFile.fileName;
const diagnostics = this.getDiagnostics(fileName);

if (diagnostics.length === 0) {
this.raiseError("Errors expected.");
}

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.find(diagnostics, d => d.code == errorCode);

return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [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.");
}

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);
Expand Down Expand Up @@ -2309,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);
Expand Down Expand Up @@ -3096,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 {
Expand Down Expand Up @@ -3275,6 +3335,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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,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: number[]): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
Expand Down
70 changes: 68 additions & 2 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);

return (<protocol.Diagnostic[]>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<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);

return (<protocol.Diagnostic[]>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[] {
Expand Down Expand Up @@ -630,6 +654,48 @@ namespace ts.server {
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<protocol.CodeFixRequest>(CommandNames.GetCodeFixes, args);
const response = this.processResponse<protocol.CodeFixResponse>(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[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
Expand Down
71 changes: 70 additions & 1 deletion src/server/protocol.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* Declaration module describing the TypeScript Server protocol
*/
declare namespace ts.server.protocol {
Expand Down Expand Up @@ -236,6 +236,53 @@ declare namespace ts.server.protocol {
position?: number;
}

/**
* Request for the available codefixes 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?: number[];
}

/**
* A request whose arguments specify a file location (file, line, col).
*/
Expand Down Expand Up @@ -1133,6 +1180,23 @@ declare namespace ts.server.protocol {
newText: string;
}

export interface FileCodeEdits {
fileName: string;
textChanges: CodeEdit[];
}

export interface CodeFixResponse extends Response {
/** The code actions that are available */
body?: 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 */
changes: FileCodeEdits[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeAction [] ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep

}

/**
* Format and format on key response message.
*/
Expand Down Expand Up @@ -1507,6 +1571,11 @@ declare namespace ts.server.protocol {
* Text of diagnostic message.
*/
text: string;

/**
* The error code of the diagnostic message.
*/
code?: number;
}

export interface DiagnosticEventBody {
Expand Down
Loading