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 21 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 @@ -3069,13 +3069,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[] {
return unwrapJSONCallResult(this.shim.getCodeFixesAtPosition(fileName, start, end, JSON.stringify(errorCodes)));
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
Expand Down
4 changes: 4 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,10 @@ 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.");
Copy link
Contributor

Choose a reason for hiding this comment

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

why? add implementation here, and add a test in tests\cases\fourslash\server

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}

getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
Expand Down
96 changes: 95 additions & 1 deletion src/server/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,53 @@ declare namespace ts.server.protocol {
position?: number;
}

/**
* Request for the available codefixed at a specific position.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit Code Fixes instead of codefixed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

*/
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 @@ -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
Expand Down Expand Up @@ -873,6 +919,18 @@ declare namespace ts.server.protocol {
newText: string;
}

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

export interface CodeActionResponse extends Response {
Copy link
Contributor

Choose a reason for hiding this comment

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

CodeFixResponse

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk Oct 7, 2016

Choose a reason for hiding this comment

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

done

/** 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 @@ -1518,4 +1576,40 @@ declare namespace ts.server.protocol {
export interface NavBarResponse extends Response {
body?: NavigationBarItem[];
}

export interface CodeAction {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is already defined above.

/**
* 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[];
Copy link
Contributor

Choose a reason for hiding this comment

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

CodeEdit [] instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

see formatting for example.

}

export interface FileTextChanges {
Copy link
Contributor

Choose a reason for hiding this comment

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

not needed.

/**
* File to apply the change to.
*/
fileName: string;

/**
* Changes to apply to the file.
*/
textChanges: TextChange[];
}

export class TextChange {
Copy link
Contributor

Choose a reason for hiding this comment

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

not needed.

/**
* 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;
}
}
53 changes: 52 additions & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends protocol.Message>(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1199,6 +1201,49 @@ 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 => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

extract the common part between here and getFormattingEditsForRange in convertTextChangeToCodeEdit

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);

Expand Down Expand Up @@ -1521,6 +1566,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));
}
});

Expand Down
Loading