Skip to content

Commit 1564137

Browse files
authored
Prototype TS/JS Refactoring Provider (#27166)
* Prototype TS/JS Refactoring Provider Fixes #25739, from microsoft/TypeScript#15569 Prototype of refactoring support for ts 2.4 * Adding error reporting * Updating for new API * show quick pick for non-inlinable refactrings
1 parent 2ddfa2e commit 1564137

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, workspace, WorkspaceEdit, window, QuickPickItem } from 'vscode';
9+
10+
import * as Proto from '../protocol';
11+
import { ITypescriptServiceClient } from '../typescriptService';
12+
13+
14+
export default class TypeScriptRefactorProvider implements CodeActionProvider {
15+
private doRefactorCommandId: string;
16+
private selectRefactorCommandId: string;
17+
18+
constructor(
19+
private readonly client: ITypescriptServiceClient,
20+
mode: string
21+
) {
22+
this.doRefactorCommandId = `_typescript.applyRefactoring.${mode}`;
23+
this.selectRefactorCommandId = `_typescript.selectRefactoring.${mode}`;
24+
25+
commands.registerCommand(this.doRefactorCommandId, this.doRefactoring, this);
26+
commands.registerCommand(this.selectRefactorCommandId, this.selectRefactoring, this);
27+
28+
}
29+
30+
public async provideCodeActions(
31+
document: TextDocument,
32+
range: Range,
33+
_context: CodeActionContext,
34+
token: CancellationToken
35+
): Promise<Command[]> {
36+
if (!this.client.apiVersion.has240Features()) {
37+
return [];
38+
}
39+
40+
const file = this.client.normalizePath(document.uri);
41+
if (!file) {
42+
return [];
43+
}
44+
45+
const args: Proto.GetApplicableRefactorsRequestArgs = {
46+
file: file,
47+
startLine: range.start.line + 1,
48+
startOffset: range.start.character + 1,
49+
endLine: range.end.line + 1,
50+
endOffset: range.end.character + 1
51+
};
52+
53+
try {
54+
const response = await this.client.execute('getApplicableRefactors', args, token);
55+
if (!response || !response.body) {
56+
return [];
57+
}
58+
59+
const actions: Command[] = [];
60+
for (const info of response.body) {
61+
if (info.inlineable === false) {
62+
actions.push({
63+
title: info.description,
64+
command: this.selectRefactorCommandId,
65+
arguments: [file, info, range]
66+
});
67+
} else {
68+
for (const action of info.actions) {
69+
actions.push({
70+
title: action.description,
71+
command: this.doRefactorCommandId,
72+
arguments: [file, info.name, action.name, range]
73+
});
74+
}
75+
}
76+
}
77+
return actions;
78+
} catch (err) {
79+
return [];
80+
}
81+
}
82+
83+
private toWorkspaceEdit(edits: Proto.FileCodeEdits[]): WorkspaceEdit {
84+
const workspaceEdit = new WorkspaceEdit();
85+
for (const edit of edits) {
86+
for (const textChange of edit.textChanges) {
87+
workspaceEdit.replace(this.client.asUrl(edit.fileName),
88+
new Range(
89+
textChange.start.line - 1, textChange.start.offset - 1,
90+
textChange.end.line - 1, textChange.end.offset - 1),
91+
textChange.newText);
92+
}
93+
}
94+
return workspaceEdit;
95+
}
96+
97+
private async selectRefactoring(file: string, info: Proto.ApplicableRefactorInfo, range: Range): Promise<boolean> {
98+
return window.showQuickPick(info.actions.map((action): QuickPickItem => ({
99+
label: action.name,
100+
description: action.description
101+
}))).then(selected => {
102+
if (!selected) {
103+
return false;
104+
}
105+
return this.doRefactoring(file, info.name, selected.label, range);
106+
});
107+
}
108+
109+
private async doRefactoring(file: string, refactor: string, action: string, range: Range): Promise<boolean> {
110+
const args: Proto.GetEditsForRefactorRequestArgs = {
111+
file,
112+
refactor,
113+
action,
114+
startLine: range.start.line + 1,
115+
startOffset: range.start.character + 1,
116+
endLine: range.end.line + 1,
117+
endOffset: range.end.character + 1
118+
};
119+
120+
const response = await this.client.execute('getEditsForRefactor', args);
121+
if (!response || !response.body || !response.body.edits.length) {
122+
return false;
123+
}
124+
125+
const edit = this.toWorkspaceEdit(response.body.edits);
126+
return workspace.applyEdit(edit);
127+
}
128+
}

extensions/typescript/src/typescriptMain.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ import BufferSyncSupport from './features/bufferSyncSupport';
3838
import CompletionItemProvider from './features/completionItemProvider';
3939
import WorkspaceSymbolProvider from './features/workspaceSymbolProvider';
4040
import CodeActionProvider from './features/codeActionProvider';
41+
import RefactorProvider from './features/refactorProvider';
4142
import ReferenceCodeLensProvider from './features/referencesCodeLensProvider';
4243
import { JsDocCompletionProvider, TryCompleteJsDocCommand } from './features/jsDocCompletionProvider';
4344
import { DirectiveCommentCompletionProvider } from './features/directiveCommentCompletionProvider';
4445
import TypeScriptTaskProviderManager from './features/taskProvider';
45-
4646
import ImplementationCodeLensProvider from './features/implementationsCodeLensProvider';
4747

4848
import * as ProjectStatus from './utils/projectStatus';
@@ -167,6 +167,7 @@ export function activate(context: ExtensionContext): void {
167167
const validateSetting = 'validate.enable';
168168

169169
class LanguageProvider {
170+
170171
private syntaxDiagnostics: ObjectMap<Diagnostic[]>;
171172
private readonly currentDiagnostics: DiagnosticCollection;
172173
private readonly bufferSyncSupport: BufferSyncSupport;
@@ -263,7 +264,7 @@ class LanguageProvider {
263264
this.disposables.push(languages.registerRenameProvider(selector, new RenameProvider(client)));
264265

265266
this.disposables.push(languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, this.description.id)));
266-
267+
this.disposables.push(languages.registerCodeActionsProvider(selector, new RefactorProvider(client, this.description.id)));
267268
this.registerVersionDependentProviders();
268269

269270
this.description.modeIds.forEach(modeId => {

extensions/typescript/src/typescriptService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export class API {
6868
public has234Features(): boolean {
6969
return semver.gte(this._version, '2.3.4');
7070
}
71+
public has240Features(): boolean {
72+
return semver.gte(this._version, '2.4.0');
73+
}
7174
}
7275

7376
export interface ITypescriptServiceClient {
@@ -115,6 +118,8 @@ export interface ITypescriptServiceClient {
115118
execute(command: 'getCodeFixes', args: Proto.CodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCodeFixesResponse>;
116119
execute(command: 'getSupportedCodeFixes', args: null, token?: CancellationToken): Promise<Proto.GetSupportedCodeFixesResponse>;
117120
execute(command: 'docCommentTemplate', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DocCommandTemplateResponse>;
121+
execute(command: 'getApplicableRefactors', args: Proto.GetApplicableRefactorsRequestArgs, token?: CancellationToken): Promise<Proto.GetApplicableRefactorsResponse>;
122+
execute(command: 'getEditsForRefactor', args: Proto.GetEditsForRefactorRequestArgs, token?: CancellationToken): Promise<Proto.GetEditsForRefactorResponse>;
118123
// execute(command: 'compileOnSaveAffectedFileList', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise<Proto.CompileOnSaveAffectedFileListResponse>;
119124
// execute(command: 'compileOnSaveEmitFile', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise<any>;
120125
execute(command: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise<any>;

0 commit comments

Comments
 (0)