Implement codefixes in tsserver

This commit is contained in:
Paul van Brenk 2016-10-04 16:58:17 -07:00
parent 20dea29e39
commit 4f404ad92b
3 changed files with 148 additions and 3 deletions

View File

@ -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;
}
}

View File

@ -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 {
@ -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));
}
});

View File

@ -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 {