Skip to content

Commit 069a2f8

Browse files
DanTupcommit-bot@chromium.org
authored andcommitted
[Analyzer] Add support to LSP server for updating imports on file renames
Fixes Dart-Code/Dart-Code#2761. Change-Id: I9e4779760a02bc91850c45aa0d0bf261ef922222 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175485 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 5d2905e commit 069a2f8

File tree

9 files changed

+223
-22
lines changed

9 files changed

+223
-22
lines changed

pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import 'package:analysis_server/src/lsp/handlers/handler_rename.dart';
3232
import 'package:analysis_server/src/lsp/handlers/handler_shutdown.dart';
3333
import 'package:analysis_server/src/lsp/handlers/handler_signature_help.dart';
3434
import 'package:analysis_server/src/lsp/handlers/handler_text_document_changes.dart';
35+
import 'package:analysis_server/src/lsp/handlers/handler_will_rename_files.dart';
3536
import 'package:analysis_server/src/lsp/handlers/handler_workspace_configuration.dart';
3637
import 'package:analysis_server/src/lsp/handlers/handler_workspace_symbols.dart';
3738
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
@@ -100,6 +101,7 @@ class InitializedStateMessageHandler extends ServerStateMessageHandler {
100101
registerHandler(WorkspaceSymbolHandler(server));
101102
registerHandler(WorkspaceDidChangeConfigurationMessageHandler(server));
102103
registerHandler(ReanalyzeHandler(server));
104+
registerHandler(WillRenameFilesHandler(server));
103105
}
104106
}
105107

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
6+
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
7+
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
8+
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
9+
import 'package:analysis_server/src/lsp/mapping.dart';
10+
import 'package:analysis_server/src/services/refactoring/refactoring.dart';
11+
12+
class WillRenameFilesHandler extends MessageHandler<RenameFilesParams, void> {
13+
WillRenameFilesHandler(LspAnalysisServer server) : super(server);
14+
@override
15+
Method get handlesMessage => Method.workspace_willRenameFiles;
16+
17+
@override
18+
LspJsonHandler<RenameFilesParams> get jsonHandler =>
19+
RenameFilesParams.jsonHandler;
20+
21+
@override
22+
Future<ErrorOr<WorkspaceEdit>> handle(
23+
RenameFilesParams params, CancellationToken token) async {
24+
final files = params?.files ?? [];
25+
// For performance reasons, only single-file rename/moves are currently supported.
26+
if (files.length > 1 || files.any((f) => !f.oldUri.endsWith('.dart'))) {
27+
return success(null);
28+
}
29+
30+
final file = files.single;
31+
final oldPath = pathOfUri(Uri.tryParse(file.oldUri));
32+
final newPath = pathOfUri(Uri.tryParse(file.newUri));
33+
return oldPath.mapResult((oldPath) =>
34+
newPath.mapResult((newPath) => _renameFile(oldPath, newPath)));
35+
}
36+
37+
Future<ErrorOr<WorkspaceEdit>> _renameFile(
38+
String oldPath, String newPath) async {
39+
final resolvedUnit = await server.getResolvedUnit(oldPath);
40+
if (resolvedUnit == null) {
41+
return success(null);
42+
}
43+
44+
final refactoring = MoveFileRefactoring(server.resourceProvider,
45+
server.refactoringWorkspace, resolvedUnit, oldPath)
46+
..newFile = newPath;
47+
48+
// If we're unable to update imports for a rename, we should silently do
49+
// nothing rather than interrupt the users file rename with an error.
50+
final results = await refactoring.checkAllConditions();
51+
if (results.hasFatalError) {
52+
return success(null);
53+
}
54+
55+
final change = await refactoring.createChange();
56+
final edit = createWorkspaceEdit(server, change.edits);
57+
58+
return success(edit);
59+
}
60+
}

pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class ClientDynamicRegistrations {
3535
Method.textDocument_codeAction,
3636
Method.textDocument_rename,
3737
Method.textDocument_foldingRange,
38+
// workspace.fileOperations covers all file operation methods but we only
39+
// support this one.
40+
Method.workspace_willRenameFiles,
3841
];
3942
final ClientCapabilities _capabilities;
4043

@@ -60,6 +63,9 @@ class ClientDynamicRegistrations {
6063
bool get documentSymbol =>
6164
_capabilities.textDocument?.documentSymbol?.dynamicRegistration ?? false;
6265

66+
bool get fileOperations =>
67+
_capabilities.workspace?.fileOperations?.dynamicRegistration ?? false;
68+
6369
bool get folding =>
6470
_capabilities.textDocument?.foldingRange?.dynamicRegistration ?? false;
6571

@@ -93,6 +99,19 @@ class ClientDynamicRegistrations {
9399
}
94100

95101
class ServerCapabilitiesComputer {
102+
static final fileOperationRegistrationOptions =
103+
FileOperationRegistrationOptions(
104+
filters: [
105+
FileOperationFilter(
106+
scheme: 'file',
107+
pattern: FileOperationPattern(
108+
glob: '**/*.dart',
109+
matches: FileOperationPatternKind.file,
110+
),
111+
)
112+
],
113+
);
114+
96115
final LspAnalysisServer _server;
97116

98117
/// Map from method name to current registration data.
@@ -213,10 +232,16 @@ class ServerCapabilitiesComputer {
213232
),
214233
workspaceSymbolProvider: Either2<bool, WorkspaceSymbolOptions>.t1(true),
215234
workspace: ServerCapabilitiesWorkspace(
216-
workspaceFolders: WorkspaceFoldersServerCapabilities(
217-
supported: true,
218-
changeNotifications: Either2<String, bool>.t2(true),
219-
)),
235+
workspaceFolders: WorkspaceFoldersServerCapabilities(
236+
supported: true,
237+
changeNotifications: Either2<String, bool>.t2(true),
238+
),
239+
fileOperations: dynamicRegistrations.fileOperations
240+
? null
241+
: ServerCapabilitiesFileOperations(
242+
willRename: fileOperationRegistrationOptions,
243+
),
244+
),
220245
);
221246
}
222247

@@ -393,6 +418,11 @@ class ServerCapabilitiesComputer {
393418
Method.textDocument_foldingRange,
394419
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
395420
);
421+
register(
422+
dynamicRegistrations.fileOperations,
423+
Method.workspace_willRenameFiles,
424+
fileOperationRegistrationOptions,
425+
);
396426
register(
397427
dynamicRegistrations.didChangeConfiguration,
398428
Method.workspace_didChangeConfiguration,

pkg/analysis_server/test/lsp/completion_dart_test.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
3434
Future<void> test_commitCharacter_completionItem() async {
3535
await provideConfig(
3636
() => initialize(
37-
textDocumentCapabilities: withAllSupportedDynamicRegistrations(
38-
emptyTextDocumentClientCapabilities),
37+
textDocumentCapabilities:
38+
withAllSupportedTextDocumentDynamicRegistrations(
39+
emptyTextDocumentClientCapabilities),
3940
workspaceCapabilities:
4041
withConfigurationSupport(emptyWorkspaceClientCapabilities),
4142
),
@@ -63,8 +64,9 @@ main() {
6364
() => monitorDynamicRegistrations(
6465
registrations,
6566
() => initialize(
66-
textDocumentCapabilities: withAllSupportedDynamicRegistrations(
67-
emptyTextDocumentClientCapabilities),
67+
textDocumentCapabilities:
68+
withAllSupportedTextDocumentDynamicRegistrations(
69+
emptyTextDocumentClientCapabilities),
6870
workspaceCapabilities:
6971
withDidChangeConfigurationDynamicRegistration(
7072
withConfigurationSupport(

pkg/analysis_server/test/lsp/initialization_test.dart

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
3838
final initResponse = await monitorDynamicRegistrations(
3939
registrations,
4040
() => initialize(
41-
// Support dynamic registration for both text sync + hovers.
42-
textDocumentCapabilities: withTextSyncDynamicRegistration(
43-
withHoverDynamicRegistration(
44-
emptyTextDocumentClientCapabilities))),
41+
// Support dynamic registration for both text sync + hovers.
42+
textDocumentCapabilities: withTextSyncDynamicRegistration(
43+
withHoverDynamicRegistration(emptyTextDocumentClientCapabilities)),
44+
// And also file operations.
45+
workspaceCapabilities: withFileOperationDynamicRegistration(
46+
emptyWorkspaceClientCapabilities),
47+
),
4548
);
4649

4750
// Because we support dynamic registration for synchronization, we won't send
@@ -53,12 +56,15 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
5356
expect(initResult.capabilities, isNotNull);
5457
expect(initResult.capabilities.textDocumentSync, isNull);
5558

56-
// Should contain Hover, DidOpen, DidClose, DidChange.
57-
expect(registrations, hasLength(4));
59+
// Should contain Hover, DidOpen, DidClose, DidChange, WillRenameFiles.
60+
expect(registrations, hasLength(5));
5861
final hover =
5962
registrationOptionsFor(registrations, Method.textDocument_hover);
6063
final change =
6164
registrationOptionsFor(registrations, Method.textDocument_didChange);
65+
final rename = FileOperationRegistrationOptions.fromJson(
66+
registrationFor(registrations, Method.workspace_willRenameFiles)
67+
?.registerOptions);
6268
expect(registrationOptionsFor(registrations, Method.textDocument_didOpen),
6369
isNotNull);
6470
expect(registrationOptionsFor(registrations, Method.textDocument_didClose),
@@ -79,6 +85,9 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
7985
change.documentSelector
8086
.any((ds) => ds.pattern == '**/analysis_options.yaml'),
8187
isTrue);
88+
89+
expect(rename,
90+
equals(ServerCapabilitiesComputer.fileOperationRegistrationOptions));
8291
}
8392

8493
Future<void> test_dynamicRegistration_notSupportedByClient() async {
@@ -120,6 +129,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
120129
expect(initResult.capabilities.codeActionProvider, isNotNull);
121130
expect(initResult.capabilities.renameProvider, isNotNull);
122131
expect(initResult.capabilities.foldingRangeProvider, isNotNull);
132+
expect(initResult.capabilities.workspace.fileOperations.willRename,
133+
equals(ServerCapabilitiesComputer.fileOperationRegistrationOptions));
123134

124135
expect(didGetRegisterCapabilityRequest, isFalse);
125136
}
@@ -147,9 +158,13 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
147158
final initResponse = await monitorDynamicRegistrations(
148159
registrations,
149160
() => initialize(
150-
// Support dynamic registration for everything we support.
151-
textDocumentCapabilities: withAllSupportedDynamicRegistrations(
152-
emptyTextDocumentClientCapabilities)),
161+
// Support dynamic registration for everything we support.
162+
textDocumentCapabilities:
163+
withAllSupportedTextDocumentDynamicRegistrations(
164+
emptyTextDocumentClientCapabilities),
165+
workspaceCapabilities: withAllSupportedWorkspaceDynamicRegistrations(
166+
emptyWorkspaceClientCapabilities),
167+
),
153168
);
154169

155170
final initResult = InitializeResult.fromJson(initResponse.result);
@@ -170,6 +185,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
170185
expect(initResult.capabilities.codeActionProvider, isNull);
171186
expect(initResult.capabilities.renameProvider, isNull);
172187
expect(initResult.capabilities.foldingRangeProvider, isNull);
188+
expect(initResult.capabilities.workspace.fileOperations, isNull);
173189

174190
// Ensure all expected dynamic registrations.
175191
for (final expectedRegistration in ClientDynamicRegistrations.supported) {
@@ -186,8 +202,9 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
186202
await monitorDynamicRegistrations(
187203
registrations,
188204
() => initialize(
189-
textDocumentCapabilities: withAllSupportedDynamicRegistrations(
190-
emptyTextDocumentClientCapabilities)),
205+
textDocumentCapabilities:
206+
withAllSupportedTextDocumentDynamicRegistrations(
207+
emptyTextDocumentClientCapabilities)),
191208
);
192209

193210
final unregisterRequest =

pkg/analysis_server/test/lsp/server_abstract.dart

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,12 @@ mixin ClientCapabilitiesHelperMixin {
199199
return ClientCapabilitiesWorkspace.fromJson(json);
200200
}
201201

202-
TextDocumentClientCapabilities withAllSupportedDynamicRegistrations(
202+
TextDocumentClientCapabilities
203+
withAllSupportedTextDocumentDynamicRegistrations(
203204
TextDocumentClientCapabilities source,
204205
) {
205-
// This list should match all of the fields listed in
206-
// `ClientDynamicRegistrations.supported`.
206+
// This list (when combined with the workspace list) should match all of
207+
// the fields listed in `ClientDynamicRegistrations.supported`.
207208
return extendTextDocumentCapabilities(source, {
208209
'synchronization': {'dynamicRegistration': true},
209210
'completion': {'dynamicRegistration': true},
@@ -224,6 +225,16 @@ mixin ClientCapabilitiesHelperMixin {
224225
});
225226
}
226227

228+
ClientCapabilitiesWorkspace withAllSupportedWorkspaceDynamicRegistrations(
229+
ClientCapabilitiesWorkspace source,
230+
) {
231+
// This list (when combined with the textDocument list) should match all of
232+
// the fields listed in `ClientDynamicRegistrations.supported`.
233+
return extendWorkspaceCapabilities(source, {
234+
'fileOperations': {'dynamicRegistration': true},
235+
});
236+
}
237+
227238
ClientCapabilitiesWorkspace withApplyEditSupport(
228239
ClientCapabilitiesWorkspace source,
229240
) {
@@ -343,6 +354,14 @@ mixin ClientCapabilitiesHelperMixin {
343354
});
344355
}
345356

357+
ClientCapabilitiesWorkspace withFileOperationDynamicRegistration(
358+
ClientCapabilitiesWorkspace source,
359+
) {
360+
return extendWorkspaceCapabilities(source, {
361+
'fileOperations': {'dynamicRegistration': true}
362+
});
363+
}
364+
346365
TextDocumentClientCapabilities withHierarchicalDocumentSymbolSupport(
347366
TextDocumentClientCapabilities source,
348367
) {
@@ -1219,6 +1238,14 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
12191238
);
12201239
}
12211240

1241+
Future<WorkspaceEdit> onWillRename(List<FileRename> renames) {
1242+
final request = makeRequest(
1243+
Method.workspace_willRenameFiles,
1244+
RenameFilesParams(files: renames),
1245+
);
1246+
return expectSuccessfulResponseTo(request, WorkspaceEdit.fromJson);
1247+
}
1248+
12221249
Future openFile(Uri uri, String content, {num version = 1}) async {
12231250
var notification = makeNotification(
12241251
Method.textDocument_didOpen,

pkg/analysis_server/test/lsp/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import 'rename_test.dart' as rename;
3737
import 'server_test.dart' as server;
3838
import 'signature_help_test.dart' as signature_help;
3939
import 'super_test.dart' as get_super;
40+
import 'will_rename_files_test.dart' as will_rename_files;
4041
import 'workspace_symbols_test.dart' as workspace_symbols;
4142

4243
void main() {
@@ -74,6 +75,7 @@ void main() {
7475
rename.main();
7576
server.main();
7677
signature_help.main();
78+
will_rename_files.main();
7779
workspace_symbols.main();
7880
}, name: 'lsp');
7981
}

0 commit comments

Comments
 (0)