mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 04:09:28 +00:00
[typescript-language-features] Add removeUnusedImports command (#161654)
* Add `removeUnusedImports` command * Continue to send `skipDestructiveCodeActions` for older TS versions * Expose Sort Imports and Remove Unused Imports commands * Update localization keys * Update for 4.9 protocol * Proto must be type only import?
This commit is contained in:
@@ -1273,6 +1273,26 @@
|
||||
"command": "typescript.goToSourceDefinition",
|
||||
"title": "%typescript.goToSourceDefinition%",
|
||||
"category": "TypeScript"
|
||||
},
|
||||
{
|
||||
"command": "typescript.sortImports",
|
||||
"title": "%typescript.sortImports%",
|
||||
"category": "TypeScript"
|
||||
},
|
||||
{
|
||||
"command": "javascript.sortImports",
|
||||
"title": "%typescript.sortImports%",
|
||||
"category": "JavaScript"
|
||||
},
|
||||
{
|
||||
"command": "typescript.removeUnusedImports",
|
||||
"title": "%typescript.removeUnusedImports%",
|
||||
"category": "TypeScript"
|
||||
},
|
||||
{
|
||||
"command": "javascript.removeUnusedImports",
|
||||
"title": "%typescript.removeUnusedImports%",
|
||||
"category": "JavaScript"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -1328,6 +1348,22 @@
|
||||
{
|
||||
"command": "typescript.goToSourceDefinition",
|
||||
"when": "tsSupportsSourceDefinition && typescript.isManagedFile"
|
||||
},
|
||||
{
|
||||
"command": "typescript.sortImports",
|
||||
"when": "tsSupportsSortImports && editorLangId =~ /^typescript(react)?$/"
|
||||
},
|
||||
{
|
||||
"command": "javascript.sortImports",
|
||||
"when": "tsSupportsSortImports && editorLangId =~ /^javascript(react)?$/"
|
||||
},
|
||||
{
|
||||
"command": "typescript.removeUnusedImports",
|
||||
"when": "tsSupportsRemoveUnusedImports && editorLangId =~ /^typescript(react)?$/"
|
||||
},
|
||||
{
|
||||
"command": "javascript.removeUnusedImports",
|
||||
"when": "tsSupportsRemoveUnusedImports && editorLangId =~ /^javascript(react)?$/"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
|
||||
@@ -187,7 +187,9 @@
|
||||
"codeActions.refactor.rewrite.parameters.toDestructured.title": "Convert parameters to destructured object",
|
||||
"codeActions.refactor.rewrite.property.generateAccessors.title": "Generate accessors",
|
||||
"codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors",
|
||||
"codeActions.source.organizeImports.title": "Organize imports",
|
||||
"codeActions.source.organizeImports.title": "Organize Imports",
|
||||
"typescript.sortImports": "Sort Imports",
|
||||
"typescript.removeUnusedImports": "Remove Unused Imports",
|
||||
"typescript.findAllFileReferences": "Find File References",
|
||||
"typescript.goToSourceDefinition": "Go to Source Definition",
|
||||
"configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace",
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, CommandManager } from '../commands/commandManager';
|
||||
import type * as Proto from '../protocol';
|
||||
import { OrganizeImportsMode } from '../protocol.const';
|
||||
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
@@ -19,17 +20,16 @@ import FileConfigurationManager from './fileConfigurationManager';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
class OrganizeImportsCommand implements Command {
|
||||
public static readonly Id = '_typescript.organizeImports';
|
||||
|
||||
public readonly id = OrganizeImportsCommand.Id;
|
||||
abstract class BaseOrganizeImportsCommand implements Command {
|
||||
protected abstract readonly mode: OrganizeImportsMode;
|
||||
|
||||
constructor(
|
||||
public id: string,
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly telemetryReporter: TelemetryReporter,
|
||||
) { }
|
||||
|
||||
public async execute(file: string, sortOnly = false): Promise<any> {
|
||||
public async execute(file?: string): Promise<any> {
|
||||
/* __GDPR__
|
||||
"organizeImports.execute" : {
|
||||
"owner": "mjbvz",
|
||||
@@ -39,6 +39,23 @@ class OrganizeImportsCommand implements Command {
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('organizeImports.execute', {});
|
||||
if (!file) {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
vscode.window.showErrorMessage(localize('error.organizeImports.noResource', "Organize Imports failed. No resource provided."));
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = activeEditor.document.uri;
|
||||
const document = await vscode.workspace.openTextDocument(resource);
|
||||
const openedFiledPath = this.client.toOpenedFilePath(document);
|
||||
if (!openedFiledPath) {
|
||||
vscode.window.showErrorMessage(localize('error.organizeImports.unknownFile', "Organize Imports failed. Unknown file type."));
|
||||
return;
|
||||
}
|
||||
|
||||
file = openedFiledPath;
|
||||
}
|
||||
|
||||
const args: Proto.OrganizeImportsRequestArgs = {
|
||||
scope: {
|
||||
@@ -47,7 +64,9 @@ class OrganizeImportsCommand implements Command {
|
||||
file
|
||||
}
|
||||
},
|
||||
skipDestructiveCodeActions: sortOnly,
|
||||
// Deprecated in 4.9; `mode` takes priority
|
||||
skipDestructiveCodeActions: this.mode === OrganizeImportsMode.SortAndCombine,
|
||||
mode: typeConverters.OrganizeImportsMode.toProtocolOrganizeImportsMode(this.mode),
|
||||
};
|
||||
const response = await this.client.interruptGetErr(() => this.client.execute('organizeImports', args, nulToken));
|
||||
if (response.type !== 'response' || !response.body) {
|
||||
@@ -61,24 +80,53 @@ class OrganizeImportsCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
class OrganizeImportsCommand extends BaseOrganizeImportsCommand {
|
||||
public static readonly id = 'organizeImports';
|
||||
public static minVersion = API.v280;
|
||||
public static title = localize('organizeImportsAction.title', "Organize Imports");
|
||||
public readonly mode = OrganizeImportsMode.All;
|
||||
}
|
||||
|
||||
class SortImportsCommand extends BaseOrganizeImportsCommand {
|
||||
public static readonly id = 'sortImports';
|
||||
public static minVersion = API.v430;
|
||||
public static title = localize('sortImportsAction.title', "Sort Imports");
|
||||
public readonly mode = OrganizeImportsMode.SortAndCombine;
|
||||
public static context = 'tsSupportsSortImports';
|
||||
}
|
||||
|
||||
class RemoveUnusedImportsCommand extends BaseOrganizeImportsCommand {
|
||||
public static readonly id = 'removeUnusedImports';
|
||||
public static minVersion = API.v490;
|
||||
public static title = localize('removeUnusedImportsAction.title', "Remove Unused Imports");
|
||||
public readonly mode = OrganizeImportsMode.RemoveUnused;
|
||||
public static context = 'tsSupportsRemoveUnusedImports';
|
||||
}
|
||||
|
||||
interface OrganizeImportsCommandClass {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly context?: string;
|
||||
readonly minVersion: API;
|
||||
new(id: string, client: ITypeScriptServiceClient, telemetryReporter: TelemetryReporter): BaseOrganizeImportsCommand;
|
||||
}
|
||||
|
||||
class ImportsCodeActionProvider implements vscode.CodeActionProvider {
|
||||
|
||||
static register(
|
||||
client: ITypeScriptServiceClient,
|
||||
minVersion: API,
|
||||
kind: vscode.CodeActionKind,
|
||||
title: string,
|
||||
sortOnly: boolean,
|
||||
Command: OrganizeImportsCommandClass,
|
||||
commandManager: CommandManager,
|
||||
fileConfigurationManager: FileConfigurationManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
selector: DocumentSelector
|
||||
): vscode.Disposable {
|
||||
return conditionalRegistration([
|
||||
requireMinVersion(client, minVersion),
|
||||
requireMinVersion(client, Command.minVersion),
|
||||
requireSomeCapability(client, ClientCapability.Semantic),
|
||||
], () => {
|
||||
const provider = new ImportsCodeActionProvider(client, kind, title, sortOnly, commandManager, fileConfigurationManager, telemetryReporter);
|
||||
const provider = new ImportsCodeActionProvider(client, kind, Command, commandManager, fileConfigurationManager, telemetryReporter);
|
||||
return vscode.languages.registerCodeActionsProvider(selector.semantic, provider, {
|
||||
providedCodeActionKinds: [kind]
|
||||
});
|
||||
@@ -88,13 +136,25 @@ class ImportsCodeActionProvider implements vscode.CodeActionProvider {
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly kind: vscode.CodeActionKind,
|
||||
private readonly title: string,
|
||||
private readonly sortOnly: boolean,
|
||||
private readonly Command: OrganizeImportsCommandClass,
|
||||
commandManager: CommandManager,
|
||||
private readonly fileConfigManager: FileConfigurationManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
) {
|
||||
commandManager.register(new OrganizeImportsCommand(client, telemetryReporter));
|
||||
commandManager.register(new Command(`typescript.${Command.id}`, client, telemetryReporter));
|
||||
if (Command !== OrganizeImportsCommand) {
|
||||
// The non-built-in variants have get duplicated with javascript-specific ids
|
||||
// can show "JavasScript" as the category
|
||||
commandManager.register(new Command(`javascript.${Command.id}`, client, telemetryReporter));
|
||||
}
|
||||
|
||||
if (Command.context) {
|
||||
updateContext();
|
||||
client.onTsServerStarted(() => updateContext());
|
||||
function updateContext() {
|
||||
vscode.commands.executeCommand('setContext', Command.context, client.apiVersion.gte(Command.minVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public provideCodeActions(
|
||||
@@ -114,8 +174,8 @@ class ImportsCodeActionProvider implements vscode.CodeActionProvider {
|
||||
|
||||
this.fileConfigManager.ensureConfigurationForDocument(document, token);
|
||||
|
||||
const action = new vscode.CodeAction(this.title, this.kind);
|
||||
action.command = { title: '', command: OrganizeImportsCommand.Id, arguments: [file, this.sortOnly] };
|
||||
const action = new vscode.CodeAction(this.Command.title, this.kind);
|
||||
action.command = { title: '', command: this.Command.id, arguments: [file] };
|
||||
return [action];
|
||||
}
|
||||
}
|
||||
@@ -130,10 +190,8 @@ export function register(
|
||||
return vscode.Disposable.from(
|
||||
ImportsCodeActionProvider.register(
|
||||
client,
|
||||
API.v280,
|
||||
vscode.CodeActionKind.SourceOrganizeImports,
|
||||
localize('organizeImportsAction.title', "Organize Imports"),
|
||||
false,
|
||||
OrganizeImportsCommand,
|
||||
commandManager,
|
||||
fileConfigurationManager,
|
||||
telemetryReporter,
|
||||
@@ -141,10 +199,17 @@ export function register(
|
||||
),
|
||||
ImportsCodeActionProvider.register(
|
||||
client,
|
||||
API.v430,
|
||||
vscode.CodeActionKind.Source.append('sortImports'),
|
||||
localize('sortImportsAction.title', "Sort Imports"),
|
||||
true,
|
||||
vscode.CodeActionKind.Source.append(SortImportsCommand.id),
|
||||
SortImportsCommand,
|
||||
commandManager,
|
||||
fileConfigurationManager,
|
||||
telemetryReporter,
|
||||
selector
|
||||
),
|
||||
ImportsCodeActionProvider.register(
|
||||
client,
|
||||
vscode.CodeActionKind.Source.append(RemoveUnusedImportsCommand.id),
|
||||
RemoveUnusedImportsCommand,
|
||||
commandManager,
|
||||
fileConfigurationManager,
|
||||
telemetryReporter,
|
||||
|
||||
@@ -89,3 +89,9 @@ export enum EventName {
|
||||
projectLoadingStart = 'projectLoadingStart',
|
||||
projectLoadingFinish = 'projectLoadingFinish',
|
||||
}
|
||||
|
||||
export enum OrganizeImportsMode {
|
||||
All = 'All',
|
||||
SortAndCombine = 'SortAndCombine',
|
||||
RemoveUnused = 'RemoveUnused',
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ export default class API {
|
||||
public static readonly v440 = API.fromSimpleString('4.4.0');
|
||||
public static readonly v460 = API.fromSimpleString('4.6.0');
|
||||
public static readonly v470 = API.fromSimpleString('4.7.0');
|
||||
public static readonly v480 = API.fromSimpleString('4.8.0');
|
||||
public static readonly v490 = API.fromSimpleString('4.9.0');
|
||||
|
||||
public static fromVersionString(versionString: string): API {
|
||||
let version = semver.valid(versionString);
|
||||
|
||||
@@ -136,3 +136,13 @@ export namespace CompletionTriggerKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace OrganizeImportsMode {
|
||||
export function toProtocolOrganizeImportsMode(mode: PConst.OrganizeImportsMode): Proto.OrganizeImportsMode {
|
||||
switch (mode) {
|
||||
case PConst.OrganizeImportsMode.All: return 'All' as Proto.OrganizeImportsMode.All;
|
||||
case PConst.OrganizeImportsMode.SortAndCombine: return 'SortAndCombine' as Proto.OrganizeImportsMode.SortAndCombine;
|
||||
case PConst.OrganizeImportsMode.RemoveUnused: return 'RemoveUnused' as Proto.OrganizeImportsMode.RemoveUnused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user