Skip to content

Commit

Permalink
Prototype autofix source code action
Browse files Browse the repository at this point in the history
Part of microsoft#62110

* Adds a new `CodeActionKind`: `source.autoFix`.
* Implements a simple auto fix provider for typescript. This provider can auto fix `implement interface` and `spelling` errors (provided there is only a single valid fix listed)

The provider is likely not something we actually want to check it (especially in its current state), we should ask TS for proper autoFix support
  • Loading branch information
mjbvz committed Jan 22, 2019
1 parent 4f8d546 commit 4e6bd4a
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 2 deletions.
133 changes: 133 additions & 0 deletions extensions/typescript-language-features/src/features/autoFix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vscode-nls';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
import { DiagnosticsManager } from './diagnostics';
import FileConfigurationManager from './fileConfigurationManager';

const localize = nls.loadMessageBundle();

const autoFixableDiagnosticCodes = new Set<number>([
2420, // Incorrectly implemented interface
2552, // Cannot find name
]);

class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {

public static readonly metadata: vscode.CodeActionProviderMetadata = {
providedCodeActionKinds: [vscode.CodeActionKind.SourceAutoFix]
};

constructor(
private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager,
private readonly diagnosticsManager: DiagnosticsManager,
) { }

public async provideCodeActions(
document: vscode.TextDocument,
_range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): Promise<vscode.CodeAction[] | undefined> {
if (!context.only || !context.only.contains(vscode.CodeActionKind.Source)) {
return undefined;
}

const file = this.client.toOpenedFilePath(document);
if (!file) {
return undefined;
}

const autoFixableDiagnostics = this.getAutoFixableDiagnostics(document);
if (!autoFixableDiagnostics.length) {
return undefined;
}

const fixAllAction = await this.getFixAllCodeAction(document, file, autoFixableDiagnostics, token);
return fixAllAction ? [fixAllAction] : undefined;
}

private getAutoFixableDiagnostics(
document: vscode.TextDocument
): vscode.Diagnostic[] {
if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
return [];
}

return this.diagnosticsManager.getDiagnostics(document.uri)
.filter(x => autoFixableDiagnosticCodes.has(x.code as number));
}

private async getFixAllCodeAction(
document: vscode.TextDocument,
file: string,
diagnostics: ReadonlyArray<vscode.Diagnostic>,
token: vscode.CancellationToken,
): Promise<vscode.CodeAction | undefined> {
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);

const autoFixResponse = await this.getAutoFixEdit(file, diagnostics, token);
if (!autoFixResponse) {
return undefined;
}
const { edit, fixedDiagnostics } = autoFixResponse;
const codeAction = new vscode.CodeAction(
localize('autoFix.label', 'Auto fix'),
vscode.CodeActionKind.SourceAutoFix);
codeAction.edit = edit;
codeAction.diagnostics = fixedDiagnostics;

return codeAction;
}

private async getAutoFixEdit(
file: string,
diagnostics: ReadonlyArray<vscode.Diagnostic>,
token: vscode.CancellationToken,
): Promise<{ edit: vscode.WorkspaceEdit, fixedDiagnostics: vscode.Diagnostic[] } | undefined> {
const edit = new vscode.WorkspaceEdit();
const fixedDiagnostics: vscode.Diagnostic[] = [];
for (const diagnostic of diagnostics) {
const args: Proto.CodeFixRequestArgs = {
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+(diagnostic.code!)]
};
const response = await this.client.execute('getCodeFixes', args, token);
if (response.type !== 'response' || !response.body || response.body.length > 1) {
return undefined;
}

const fix = response.body[0];
if (new Set<string>(['fixClassIncorrectlyImplementsInterface', 'spelling']).has(fix.fixName)) {
typeConverters.WorkspaceEdit.withFileCodeEdits(edit, this.client, fix.changes);
fixedDiagnostics.push(diagnostic);
}
}

if (!fixedDiagnostics.length) {
return undefined;
}

return { edit, fixedDiagnostics };
}
}

export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager,
diagnosticsManager: DiagnosticsManager) {
return new VersionDependentRegistration(client, API.v213, () =>
vscode.languages.registerCodeActionsProvider(selector,
new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager),
TypeScriptAutoFixProvider.metadata));
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default class LanguageProvider extends Disposable {
this._register((await import('./features/jsDocCompletions')).register(selector, this.client));
this._register((await import('./features/organizeImports')).register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter));
this._register((await import('./features/quickFix')).register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter));
this._register((await import('./features/autoFix')).register(selector, this.client, this.fileConfigurationManager, this.client.diagnosticsManager));
this._register((await import('./features/refactor')).register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter));
this._register((await import('./features/references')).register(selector, this.client));
this._register((await import('./features/referencesCodeLens')).register(selector, this.description.id, this.client, cachedResponse));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface TypeScriptRequestTypes {
'format': [Proto.FormatRequestArgs, Proto.FormatResponse];
'formatonkey': [Proto.FormatOnKeyRequestArgs, Proto.FormatResponse];
'getApplicableRefactors': [Proto.GetApplicableRefactorsRequestArgs, Proto.GetApplicableRefactorsResponse];
'getCodeFixes': [Proto.CodeFixRequestArgs, Proto.GetCodeFixesResponse];
'getCodeFixes': [Proto.CodeFixRequestArgs, Proto.CodeFixResponse];
'getCombinedCodeFix': [Proto.GetCombinedCodeFixRequestArgs, Proto.GetCombinedCodeFixResponse];
'getEditsForFileRename': [Proto.GetEditsForFileRenameRequestArgs, Proto.GetEditsForFileRenameResponse];
'getEditsForRefactor': [Proto.GetEditsForRefactorRequestArgs, Proto.GetEditsForRefactorResponse];
Expand Down
4 changes: 4 additions & 0 deletions src/vs/editor/common/config/commonEditorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@ const editorConfiguration: IConfigurationNode = {
'source.organizeImports': {
'type': 'boolean',
'description': nls.localize('codeActionsOnSave.organizeImports', "Controls whether organize imports action should be run on file save.")
},
'source.autoFix': {
'type': 'boolean',
'description': nls.localize('codeActionsOnSave.autoFix', "Controls whether auto fix action should be run on file save.")
}
},
'additionalProperties': {
Expand Down
10 changes: 9 additions & 1 deletion src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ declare module 'vscode' {
//#region CodeAction.isPreferred - mjbvz
export interface CodeAction {
/**
* If the action is a prefered action or fix to take.
* If the action is a preferred action or fix to take.
*
* A quick fix should be marked preferred if it properly addresses the underlying error.
* A refactoring should be marked preferred if it is the most reasonable choice of actions to take.
Expand All @@ -1123,4 +1123,12 @@ declare module 'vscode' {
//#endregion


//#region Autofix - mjbvz
export namespace CodeActionKind {
/**
* Base kind for an auto fix source action: `source.autoFix`.
*/
export const SourceAutoFix: CodeActionKind;
}
//#endregion
}
1 change: 1 addition & 0 deletions src/vs/workbench/api/node/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ export class CodeActionKind {
public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite');
public static readonly Source = CodeActionKind.Empty.append('source');
public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
public static readonly SourceAutoFix = CodeActionKind.Source.append('autoFix');

constructor(
public readonly value: string
Expand Down

0 comments on commit 4e6bd4a

Please sign in to comment.