Add quick fix all for JS/TS

Fixes #40170
This commit is contained in:
Matt Bierner
2018-01-17 15:21:40 -08:00
parent 253434c87c
commit b0a26a94c6
5 changed files with 117 additions and 25 deletions

View File

@@ -11,6 +11,10 @@ import { vsRangeToTsFileRange } from '../utils/convert';
import FormattingConfigurationManager from './formattingConfigurationManager';
import { getEditForCodeAction, applyCodeActionCommands } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { createWorkspaceEditFromFileCodeEdits } from '../utils/workspaceEdit';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class ApplyCodeActionCommand implements Command {
public static readonly ID = '_typescript.applyCodeActionCommand';
@@ -87,23 +91,50 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv
const results: vscode.CodeAction[] = [];
for (const diagnostic of fixableDiagnostics) {
const args: Proto.CodeFixRequestArgs = {
...vsRangeToTsFileRange(file, diagnostic.range),
errorCodes: [+diagnostic.code]
};
const response = await this.client.execute('getCodeFixes', args, token);
if (response.body) {
results.push(...response.body.map(action => this.getCommandForAction(diagnostic, action)));
}
results.push(...await this.getFixesForDiagnostic(file, diagnostic, token));
}
return results;
}
private getCommandForAction(
private async getFixesForDiagnostic(
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeAction
token: vscode.CancellationToken
): Promise<Iterable<vscode.CodeAction>> {
const args: Proto.CodeFixRequestArgs = {
...vsRangeToTsFileRange(file, diagnostic.range),
errorCodes: [+diagnostic.code]
};
const codeFixesResponse = await this.client.execute('getCodeFixes', args, token);
if (codeFixesResponse.body) {
const results: vscode.CodeAction[] = [];
for (const tsCodeFix of codeFixesResponse.body) {
results.push(...await this.getAllFixesForTsCodeAction(file, diagnostic, tsCodeFix, token));
}
return results;
}
return [];
}
private async getAllFixesForTsCodeAction(
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction,
token: vscode.CancellationToken
): Promise<Iterable<vscode.CodeAction>> {
const singleFix = this.getSingleFixForTsCodeAction(diagnostic, tsAction);
const fixAll = await this.getFixAllForTsCodeAction(file, diagnostic, tsAction, token);
return fixAll ? [singleFix, fixAll] : [singleFix];
}
private getSingleFixForTsCodeAction(
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction
): vscode.CodeAction {
const codeAction = new vscode.CodeAction(tsAction.description, getEditForCodeAction(this.client, tsAction));
const codeAction = new vscode.CodeAction(
tsAction.description,
getEditForCodeAction(this.client, tsAction));
codeAction.diagnostics = [diagnostic];
if (tsAction.commands) {
codeAction.command = {
@@ -114,4 +145,45 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv
}
return codeAction;
}
private async getFixAllForTsCodeAction(
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction,
token: vscode.CancellationToken
): Promise<vscode.CodeAction | undefined> {
if (!tsAction.fixId || !this.client.apiVersion.has270Features()) {
return undefined;
}
const args: Proto.GetCombinedCodeFixRequestArgs = {
scope: {
type: 'file',
args: { file }
},
fixId: tsAction.fixId
};
try {
const combinedCodeFixesResponse = await this.client.execute('getCombinedCodeFix', args, token);
if (!combinedCodeFixesResponse.body) {
return undefined;
}
const codeAction = new vscode.CodeAction(
localize('fixAllInFileLabel', '{0} (Fix all in file)', tsAction.description),
createWorkspaceEditFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes));
codeAction.diagnostics = [diagnostic];
if (tsAction.commands) {
codeAction.command = {
command: ApplyCodeActionCommand.ID,
arguments: [tsAction],
title: tsAction.description
};
}
return codeAction;
} catch {
return undefined;
}
}
}

View File

@@ -61,6 +61,7 @@ export interface ITypeScriptServiceClient {
execute(command: 'navtree', args: Proto.FileRequestArgs, token?: CancellationToken): Promise<Proto.NavTreeResponse>;
execute(command: 'getCodeFixes', args: Proto.CodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCodeFixesResponse>;
execute(command: 'getSupportedCodeFixes', args: null, token?: CancellationToken): Promise<Proto.GetSupportedCodeFixesResponse>;
execute(command: 'getCombinedCodeFix', args: Proto.GetCombinedCodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCombinedCodeFixResponse>;
execute(command: 'docCommentTemplate', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DocCommandTemplateResponse>;
execute(command: 'getApplicableRefactors', args: Proto.GetApplicableRefactorsRequestArgs, token?: CancellationToken): Promise<Proto.GetApplicableRefactorsResponse>;
execute(command: 'getEditsForRefactor', args: Proto.GetEditsForRefactorRequestArgs, token?: CancellationToken): Promise<Proto.GetEditsForRefactorResponse>;

View File

@@ -91,4 +91,9 @@ export default class API {
public has262Features(): boolean {
return semver.gte(this.version, '2.6.2');
}
@memoize
public has270Features(): boolean {
return semver.gte(this.version, '2.7.0');
}
}

View File

@@ -5,26 +5,16 @@
import { WorkspaceEdit, workspace } from 'vscode';
import * as Proto from '../protocol';
import { tsTextSpanToVsRange } from './convert';
import { ITypeScriptServiceClient } from '../typescriptService';
import { createWorkspaceEditFromFileCodeEdits } from './workspaceEdit';
export function getEditForCodeAction(
client: ITypeScriptServiceClient,
action: Proto.CodeAction
): WorkspaceEdit | undefined {
if (action.changes && action.changes.length) {
const workspaceEdit = new WorkspaceEdit();
for (const change of action.changes) {
for (const textChange of change.textChanges) {
workspaceEdit.replace(client.asUrl(change.fileName),
tsTextSpanToVsRange(textChange),
textChange.newText);
}
}
return workspaceEdit;
}
return undefined;
return action.changes && action.changes.length
? createWorkspaceEditFromFileCodeEdits(client, action.changes)
: undefined;
}
export async function applyCodeAction(

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as Proto from '../protocol';
import { tsTextSpanToVsRange } from './convert';
export function createWorkspaceEditFromFileCodeEdits(
client: ITypeScriptServiceClient,
edits: Iterable<Proto.FileCodeEdits>
): vscode.WorkspaceEdit {
const workspaceEdit = new vscode.WorkspaceEdit();
for (const edit of edits) {
for (const textChange of edit.textChanges) {
workspaceEdit.replace(client.asUrl(edit.fileName),
tsTextSpanToVsRange(textChange),
textChange.newText);
}
}
return workspaceEdit;
}