mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 03:54:24 +01:00
Cleaning up fix all for JS/TS
This commit is contained in:
@@ -8,24 +8,32 @@ import * as nls from 'vscode-nls';
|
||||
import type * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { DiagnosticsManager } from './diagnostics';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
import * as errorCodes from '../utils/errorCodes';
|
||||
import * as fixNames from '../utils/fixNames';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const autoFixableDiagnosticCodes = new Set<number>([
|
||||
2420, // Incorrectly implemented interface
|
||||
2552, // Cannot find name
|
||||
]);
|
||||
interface AutoFixableError {
|
||||
readonly code: number;
|
||||
readonly fixName: string;
|
||||
}
|
||||
|
||||
const fixImplementInterface = Object.freeze<AutoFixableError>({ code: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface });
|
||||
const fixUnreachable = Object.freeze<AutoFixableError>({ code: errorCodes.unreachableCode, fixName: fixNames.unreachableCode });
|
||||
const fixAsync = Object.freeze<AutoFixableError>({ code: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction });
|
||||
|
||||
class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
private static readonly kind = vscode.CodeActionKind.SourceFixAll.append('ts');
|
||||
private static readonly fixAllKind = vscode.CodeActionKind.SourceFixAll.append('ts');
|
||||
|
||||
public static readonly metadata: vscode.CodeActionProviderMetadata = {
|
||||
providedCodeActionKinds: [TypeScriptAutoFixProvider.kind]
|
||||
providedCodeActionKinds: [
|
||||
TypeScriptAutoFixProvider.fixAllKind,
|
||||
]
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -49,35 +57,40 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const autoFixableDiagnostics = this.getAutoFixableDiagnostics(document);
|
||||
const autoFixes = this.getAutoFixes();
|
||||
|
||||
const autoFixableDiagnostics = this.getAutoFixableDiagnostics(document, autoFixes);
|
||||
if (!autoFixableDiagnostics.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fixAllAction = await this.getFixAllCodeAction(document, file, autoFixableDiagnostics, token);
|
||||
const fixAllAction = await this.getFixAllCodeAction(document, file, autoFixableDiagnostics, autoFixes, token);
|
||||
return fixAllAction ? [fixAllAction] : undefined;
|
||||
}
|
||||
|
||||
private getAutoFixableDiagnostics(
|
||||
document: vscode.TextDocument
|
||||
document: vscode.TextDocument,
|
||||
autoFixes: readonly AutoFixableError[],
|
||||
): vscode.Diagnostic[] {
|
||||
if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fixableCodes = new Set(autoFixes.map(x => x.code));
|
||||
return this.diagnosticsManager.getDiagnostics(document.uri)
|
||||
.filter(x => autoFixableDiagnosticCodes.has(x.code as number));
|
||||
.filter(x => fixableCodes.has(+(x.code as number)));
|
||||
}
|
||||
|
||||
private async getFixAllCodeAction(
|
||||
document: vscode.TextDocument,
|
||||
file: string,
|
||||
diagnostics: ReadonlyArray<vscode.Diagnostic>,
|
||||
autoFixes: readonly AutoFixableError[],
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.CodeAction | undefined> {
|
||||
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
|
||||
|
||||
const autoFixResponse = await this.getAutoFixEdit(file, diagnostics, token);
|
||||
const autoFixResponse = await this.getAutoFixEdit(file, diagnostics, autoFixes, token);
|
||||
if (!autoFixResponse) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -88,7 +101,7 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
const codeAction = new vscode.CodeAction(
|
||||
localize('autoFix.label', 'Auto fix'),
|
||||
TypeScriptAutoFixProvider.kind);
|
||||
TypeScriptAutoFixProvider.fixAllKind);
|
||||
codeAction.edit = edit;
|
||||
codeAction.diagnostics = fixedDiagnostics;
|
||||
|
||||
@@ -98,6 +111,7 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
private async getAutoFixEdit(
|
||||
file: string,
|
||||
diagnostics: ReadonlyArray<vscode.Diagnostic>,
|
||||
autoFixes: readonly AutoFixableError[],
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<{ edit: vscode.WorkspaceEdit, fixedDiagnostics: vscode.Diagnostic[] } | undefined> {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
@@ -113,7 +127,7 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
}
|
||||
|
||||
const fix = response.body[0];
|
||||
if (['fixClassIncorrectlyImplementsInterface', 'spelling'].includes(fix.fixName)) {
|
||||
if (autoFixes.some(autoFix => autoFix.fixName.includes(fix.fixName))) {
|
||||
typeConverters.WorkspaceEdit.withFileCodeEdits(edit, this.client, fix.changes);
|
||||
fixedDiagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -125,6 +139,14 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
return { edit, fixedDiagnostics };
|
||||
}
|
||||
|
||||
private getAutoFixes(): readonly AutoFixableError[] {
|
||||
return [
|
||||
fixImplementInterface,
|
||||
fixUnreachable,
|
||||
fixAsync,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function register(
|
||||
@@ -133,8 +155,7 @@ export function register(
|
||||
fileConfigurationManager: FileConfigurationManager,
|
||||
diagnosticsManager: DiagnosticsManager) {
|
||||
return new VersionDependentRegistration(client, API.v300, () =>
|
||||
new ConfigurationDependentRegistration('typescript', 'experimental.autoFix.enabled', () =>
|
||||
vscode.languages.registerCodeActionsProvider(selector,
|
||||
new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager),
|
||||
TypeScriptAutoFixProvider.metadata)));
|
||||
vscode.languages.registerCodeActionsProvider(selector,
|
||||
new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager),
|
||||
TypeScriptAutoFixProvider.metadata));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import API from '../utils/api';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import * as fixNames from '../utils/fixNames';
|
||||
import { memoize } from '../utils/memoize';
|
||||
import { TelemetryReporter } from '../utils/telemetry';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
@@ -332,18 +333,17 @@ const fixAllErrorCodes = new Map<number, number>([
|
||||
[2345, 2339],
|
||||
]);
|
||||
|
||||
|
||||
const preferredFixes = new Map<string, /* priorty */number>([
|
||||
['annotateWithTypeFromJSDoc', 0],
|
||||
['constructorForDerivedNeedSuperCall', 0],
|
||||
['extendsInterfaceBecomesImplements', 0],
|
||||
['fixAwaitInSyncFunction', 0],
|
||||
['fixClassIncorrectlyImplementsInterface', 1],
|
||||
['fixUnreachableCode', 0],
|
||||
['unusedIdentifier', 0],
|
||||
['forgottenThisPropertyAccess', 0],
|
||||
['spelling', 1],
|
||||
['addMissingAwait', 0],
|
||||
[fixNames.annotateWithTypeFromJSDoc, 0],
|
||||
[fixNames.constructorForDerivedNeedSuperCall, 0],
|
||||
[fixNames.extendsInterfaceBecomesImplements, 0],
|
||||
[fixNames.awaitInSyncFunction, 0],
|
||||
[fixNames.classIncorrectlyImplementsInterface, 1],
|
||||
[fixNames.unreachableCode, 0],
|
||||
[fixNames.unusedIdentifier, 0],
|
||||
[fixNames.forgottenThisPropertyAccess, 0],
|
||||
[fixNames.spelling, 1],
|
||||
[fixNames.addMissingAwait, 0],
|
||||
]);
|
||||
|
||||
function isPreferredFix(
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { disposeAll } from '../utils/dispose';
|
||||
import { createTestEditor, wait, joinLines } from './testUtils';
|
||||
|
||||
const testDocumentUri = vscode.Uri.parse('untitled:test.ts');
|
||||
|
||||
const emptyRange = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0));
|
||||
|
||||
suite('TypeScript Fix All', () => {
|
||||
|
||||
const _disposables: vscode.Disposable[] = [];
|
||||
|
||||
teardown(async () => {
|
||||
disposeAll(_disposables);
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
});
|
||||
|
||||
test('Should fix unreachable code', async () => {
|
||||
const editor = await createTestEditor(testDocumentUri,
|
||||
`function foo() {`,
|
||||
` return 1;`,
|
||||
` return 2;`,
|
||||
`};`,
|
||||
`function boo() {`,
|
||||
` return 3;`,
|
||||
` return 4;`,
|
||||
`};`,
|
||||
);
|
||||
|
||||
await wait(2500);
|
||||
|
||||
const fixes = await vscode.commands.executeCommand<vscode.CodeAction[]>('vscode.executeCodeActionProvider',
|
||||
testDocumentUri,
|
||||
emptyRange,
|
||||
vscode.CodeActionKind.SourceFixAll
|
||||
);
|
||||
assert.strictEqual(fixes?.length, 1);
|
||||
|
||||
await vscode.workspace.applyEdit(fixes![0].edit!);
|
||||
assert.strictEqual(editor.document.getText(), joinLines(
|
||||
`function foo() {`,
|
||||
` return 1;`,
|
||||
`};`,
|
||||
`function boo() {`,
|
||||
` return 3;`,
|
||||
`};`,
|
||||
));
|
||||
});
|
||||
|
||||
test('Should implement interfaces', async () => {
|
||||
const editor = await createTestEditor(testDocumentUri,
|
||||
`interface I {`,
|
||||
` x: number;`,
|
||||
`}`,
|
||||
`class A implements I {}`,
|
||||
`class B implements I {}`,
|
||||
);
|
||||
|
||||
await wait(2500);
|
||||
|
||||
const fixes = await vscode.commands.executeCommand<vscode.CodeAction[]>('vscode.executeCodeActionProvider',
|
||||
testDocumentUri,
|
||||
emptyRange,
|
||||
vscode.CodeActionKind.SourceFixAll
|
||||
);
|
||||
assert.strictEqual(fixes?.length, 1);
|
||||
|
||||
await vscode.workspace.applyEdit(fixes![0].edit!);
|
||||
assert.strictEqual(editor.document.getText(), joinLines(
|
||||
`interface I {`,
|
||||
` x: number;`,
|
||||
`}`,
|
||||
`class A implements I {`,
|
||||
` x: number;`,
|
||||
`}`,
|
||||
`class B implements I {`,
|
||||
` x: number;`,
|
||||
`}`,
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -10,3 +10,7 @@ export const unreachableCode = 7027;
|
||||
export const unusedLabel = 7028;
|
||||
export const fallThroughCaseInSwitch = 7029;
|
||||
export const notAllCodePathsReturnAValue = 7030;
|
||||
export const incorrectlyImplementsInterface = 2420;
|
||||
export const cannotFindName = 2552;
|
||||
export const extendsShouldBeImplements = 2689;
|
||||
export const asyncOnlyAllowedInAsyncFunctions = 1308;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const annotateWithTypeFromJSDoc = 'annotateWithTypeFromJSDoc';
|
||||
export const constructorForDerivedNeedSuperCall = 'constructorForDerivedNeedSuperCall';
|
||||
export const extendsInterfaceBecomesImplements = 'extendsInterfaceBecomesImplements';
|
||||
export const awaitInSyncFunction = 'fixAwaitInSyncFunction';
|
||||
export const classIncorrectlyImplementsInterface = 'fixClassIncorrectlyImplementsInterface';
|
||||
export const unreachableCode = 'fixUnreachableCode';
|
||||
export const unusedIdentifier = 'unusedIdentifier';
|
||||
export const forgottenThisPropertyAccess = 'forgottenThisPropertyAccess';
|
||||
export const spelling = 'spelling';
|
||||
export const addMissingAwait = 'addMissingAwait';
|
||||
Reference in New Issue
Block a user