Skip to content

Commit ce3b7c2

Browse files
srawlinsCommit Queue
authored and
Commit Queue
committed
DAS plugins: support overlay changes
This adds and tests support for adding file overlays, changing them, and removing them. Work towards #53402 Change-Id: I7939f8199639f7e336cd97515c2f7d48b1d777a0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384305 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 1967514 commit ce3b7c2

File tree

2 files changed

+233
-46
lines changed

2 files changed

+233
-46
lines changed

pkg/analysis_server_plugin/lib/src/plugin_server.dart

Lines changed: 126 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class PluginServer {
6969
/// The recent state of analysis reults, to be cleared on file changes.
7070
final _recentState = <String, _PluginState>{};
7171

72+
/// The next modification stamp for a changed file in the [resourceProvider].
73+
int _overlayModificationStamp = 0;
74+
7275
PluginServer({
7376
required ResourceProvider resourceProvider,
7477
required List<Plugin> plugins,
@@ -79,29 +82,6 @@ class PluginServer {
7982
}
8083
}
8184

82-
/// Handles an 'analysis.setContextRoots' request.
83-
Future<protocol.AnalysisSetContextRootsResult> handleAnalysisSetContextRoots(
84-
protocol.AnalysisSetContextRootsParams parameters) async {
85-
var currentContextCollection = _contextCollection;
86-
if (currentContextCollection != null) {
87-
_contextCollection = null;
88-
await currentContextCollection.dispose();
89-
}
90-
91-
var includedPaths = parameters.roots.map((e) => e.root).toList();
92-
var contextCollection = AnalysisContextCollectionImpl(
93-
resourceProvider: _resourceProvider,
94-
includedPaths: includedPaths,
95-
byteStore: _byteStore,
96-
sdkPath: _sdkPath,
97-
fileContentCache: FileContentCache(_resourceProvider),
98-
);
99-
_contextCollection = contextCollection;
100-
await _analyzeAllFilesInContextCollection(
101-
contextCollection: contextCollection);
102-
return protocol.AnalysisSetContextRootsResult();
103-
}
104-
10585
/// Handles an 'edit.getFixes' request.
10686
///
10787
/// Throws a [RequestFailure] if the request could not be handled.
@@ -193,8 +173,6 @@ class PluginServer {
193173

194174
/// This method is invoked when a new instance of [AnalysisContextCollection]
195175
/// is created, so the plugin can perform initial analysis of analyzed files.
196-
///
197-
/// By default analyzes every [AnalysisContext] with [_analyzeFiles].
198176
Future<void> _analyzeAllFilesInContextCollection({
199177
required AnalysisContextCollection contextCollection,
200178
}) async {
@@ -326,27 +304,36 @@ class PluginServer {
326304
case protocol.ANALYSIS_REQUEST_GET_NAVIGATION:
327305
case protocol.ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS:
328306
result = null;
307+
329308
case protocol.ANALYSIS_REQUEST_SET_CONTEXT_ROOTS:
330309
var params =
331310
protocol.AnalysisSetContextRootsParams.fromRequest(request);
332-
result = await handleAnalysisSetContextRoots(params);
311+
result = await _handleAnalysisSetContextRoots(params);
312+
333313
case protocol.ANALYSIS_REQUEST_SET_PRIORITY_FILES:
334314
case protocol.ANALYSIS_REQUEST_SET_SUBSCRIPTIONS:
335315
case protocol.ANALYSIS_REQUEST_UPDATE_CONTENT:
316+
var params = protocol.AnalysisUpdateContentParams.fromRequest(request);
317+
result = await _handleAnalysisUpdateContent(params);
318+
336319
case protocol.COMPLETION_REQUEST_GET_SUGGESTIONS:
337320
case protocol.EDIT_REQUEST_GET_ASSISTS:
338321
case protocol.EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS:
339322
result = null;
323+
340324
case protocol.EDIT_REQUEST_GET_FIXES:
341325
var params = protocol.EditGetFixesParams.fromRequest(request);
342326
result = await handleEditGetFixes(params);
327+
343328
case protocol.EDIT_REQUEST_GET_REFACTORING:
344329
result = null;
330+
345331
case protocol.PLUGIN_REQUEST_SHUTDOWN:
346332
_channel.sendResponse(protocol.PluginShutdownResult()
347333
.toResponse(request.id, requestTime));
348334
_channel.close();
349335
return null;
336+
350337
case protocol.PLUGIN_REQUEST_VERSION_CHECK:
351338
var params = protocol.PluginVersionCheckParams.fromRequest(request);
352339
result = await handlePluginVersionCheck(params);
@@ -358,6 +345,119 @@ class PluginServer {
358345
return result.toResponse(request.id, requestTime);
359346
}
360347

348+
/// Handles files that might have been affected by a content change of
349+
/// one or more files. The implementation may check if these files should
350+
/// be analyzed, do such analysis, and send diagnostics.
351+
///
352+
/// By default invokes [_analyzeFiles] only for files that are analyzed in
353+
/// this [analysisContext].
354+
Future<void> _handleAffectedFiles({
355+
required AnalysisContext analysisContext,
356+
required List<String> paths,
357+
}) async {
358+
var analyzedPaths = paths
359+
.where(analysisContext.contextRoot.isAnalyzed)
360+
.toList(growable: false);
361+
362+
await _analyzeFiles(
363+
analysisContext: analysisContext,
364+
paths: analyzedPaths,
365+
);
366+
}
367+
368+
/// Handles an 'analysis.setContextRoots' request.
369+
Future<protocol.AnalysisSetContextRootsResult> _handleAnalysisSetContextRoots(
370+
protocol.AnalysisSetContextRootsParams parameters) async {
371+
var currentContextCollection = _contextCollection;
372+
if (currentContextCollection != null) {
373+
_contextCollection = null;
374+
await currentContextCollection.dispose();
375+
}
376+
377+
var includedPaths = parameters.roots.map((e) => e.root).toList();
378+
var contextCollection = AnalysisContextCollectionImpl(
379+
resourceProvider: _resourceProvider,
380+
includedPaths: includedPaths,
381+
byteStore: _byteStore,
382+
sdkPath: _sdkPath,
383+
fileContentCache: FileContentCache(_resourceProvider),
384+
);
385+
_contextCollection = contextCollection;
386+
await _analyzeAllFilesInContextCollection(
387+
contextCollection: contextCollection);
388+
return protocol.AnalysisSetContextRootsResult();
389+
}
390+
391+
/// Handles an 'analysis.updateContent' request.
392+
///
393+
/// Throws a [RequestFailure] if the request could not be handled.
394+
Future<protocol.AnalysisUpdateContentResult> _handleAnalysisUpdateContent(
395+
protocol.AnalysisUpdateContentParams parameters) async {
396+
var changedPaths = <String>{};
397+
var paths = parameters.files;
398+
paths.forEach((String path, Object overlay) {
399+
// Prepare the old overlay contents.
400+
String? oldContent;
401+
try {
402+
if (_resourceProvider.hasOverlay(path)) {
403+
oldContent = _resourceProvider.getFile(path).readAsStringSync();
404+
}
405+
} catch (_) {
406+
// Leave `oldContent` empty.
407+
}
408+
409+
// Prepare the new contents.
410+
String? newContent;
411+
if (overlay is protocol.AddContentOverlay) {
412+
newContent = overlay.content;
413+
} else if (overlay is protocol.ChangeContentOverlay) {
414+
if (oldContent == null) {
415+
// The server should only send a ChangeContentOverlay if there is
416+
// already an existing overlay for the source.
417+
throw RequestFailure(
418+
RequestErrorFactory.invalidOverlayChangeNoContent());
419+
}
420+
try {
421+
newContent =
422+
protocol.SourceEdit.applySequence(oldContent, overlay.edits);
423+
} on RangeError {
424+
throw RequestFailure(
425+
RequestErrorFactory.invalidOverlayChangeInvalidEdit());
426+
}
427+
} else if (overlay is protocol.RemoveContentOverlay) {
428+
newContent = null;
429+
}
430+
431+
if (newContent != null) {
432+
_resourceProvider.setOverlay(
433+
path,
434+
content: newContent,
435+
modificationStamp: _overlayModificationStamp++,
436+
);
437+
} else {
438+
_resourceProvider.removeOverlay(path);
439+
}
440+
441+
changedPaths.add(path);
442+
});
443+
await _handleContentChanged(changedPaths.toList());
444+
return protocol.AnalysisUpdateContentResult();
445+
}
446+
447+
/// Handles the fact that files with [paths] were changed.
448+
Future<void> _handleContentChanged(List<String> paths) async {
449+
if (_contextCollection case var contextCollection?) {
450+
await _forAnalysisContexts(contextCollection, (analysisContext) async {
451+
for (var path in paths) {
452+
analysisContext.changeFile(path);
453+
}
454+
var affected = await analysisContext.applyPendingFileChanges();
455+
await _handleAffectedFiles(
456+
analysisContext: analysisContext, paths: affected);
457+
});
458+
}
459+
}
460+
361461
Future<void> _handleRequest(Request request) async {
362462
var requestTime = DateTime.now().millisecondsSinceEpoch;
363463
var id = request.id;

pkg/analysis_server_plugin/test/src/plugin_server_test.dart

Lines changed: 107 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
1212
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
1313
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
1414
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
15+
import 'package:async/async.dart';
1516
import 'package:test/test.dart';
1617
import 'package:test_reflective_loader/test_reflective_loader.dart';
1718

@@ -24,6 +25,12 @@ void main() async {
2425

2526
@reflectiveTest
2627
class PluginServerTest extends PluginServerTestBase {
28+
protocol.ContextRoot get contextRoot => protocol.ContextRoot(packagePath, []);
29+
30+
String get filePath => join(packagePath, 'lib', 'test.dart');
31+
32+
String get packagePath => convertPath('/package1');
33+
2734
@override
2835
Future<void> setUp() async {
2936
await super.setUp();
@@ -34,35 +41,20 @@ class PluginServerTest extends PluginServerTestBase {
3441
}
3542

3643
Future<void> test_handleAnalysisSetContextRoots() async {
37-
var packagePath = convertPath('/package1');
38-
var filePath = join(packagePath, 'lib', 'test.dart');
3944
newFile(filePath, 'bool b = false;');
40-
var contextRoot = protocol.ContextRoot(packagePath, []);
41-
await pluginServer.handleAnalysisSetContextRoots(
42-
protocol.AnalysisSetContextRootsParams([contextRoot]));
45+
await channel
46+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
4347
var notification = await channel.notifications.first;
4448
var params = protocol.AnalysisErrorsParams.fromNotification(notification);
4549
expect(params.file, convertPath('/package1/lib/test.dart'));
4650
expect(params.errors, hasLength(1));
47-
48-
expect(
49-
params.errors.single,
50-
isA<protocol.AnalysisError>()
51-
.having((e) => e.severity, 'severity',
52-
protocol.AnalysisErrorSeverity.INFO)
53-
.having(
54-
(e) => e.type, 'type', protocol.AnalysisErrorType.STATIC_WARNING)
55-
.having((e) => e.message, 'message', 'No bools message'),
56-
);
51+
_expectAnalysisError(params.errors.single, message: 'No bools message');
5752
}
5853

5954
Future<void> test_handleEditGetFixes() async {
60-
var packagePath = convertPath('/package1');
61-
var filePath = join(packagePath, 'lib', 'test.dart');
6255
newFile(filePath, 'bool b = false;');
63-
var contextRoot = protocol.ContextRoot(packagePath, []);
64-
await pluginServer.handleAnalysisSetContextRoots(
65-
protocol.AnalysisSetContextRootsParams([contextRoot]));
56+
await channel
57+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
6658

6759
var result = await pluginServer.handleEditGetFixes(
6860
protocol.EditGetFixesParams(filePath, 'bool b = '.length));
@@ -73,6 +65,101 @@ class PluginServerTest extends PluginServerTestBase {
7365
expect(fixes, hasLength(1));
7466
expect(fixes[0].fixes, hasLength(1));
7567
}
68+
69+
Future<void> test_updateContent_addOverlay() async {
70+
newFile(filePath, 'int b = 7;');
71+
await channel
72+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
73+
74+
var notifications = StreamQueue(channel.notifications);
75+
var notification = await notifications.next;
76+
var params = protocol.AnalysisErrorsParams.fromNotification(notification);
77+
expect(params.file, convertPath('/package1/lib/test.dart'));
78+
expect(params.errors, isEmpty);
79+
80+
await channel.sendRequest(protocol.AnalysisUpdateContentParams(
81+
{filePath: protocol.AddContentOverlay('bool b = false;')}));
82+
83+
notification = await notifications.next;
84+
params = protocol.AnalysisErrorsParams.fromNotification(notification);
85+
expect(params.file, convertPath('/package1/lib/test.dart'));
86+
expect(params.errors, hasLength(1));
87+
_expectAnalysisError(params.errors.single, message: 'No bools message');
88+
}
89+
90+
Future<void> test_updateContent_changeOverlay() async {
91+
newFile(filePath, 'int b = 7;');
92+
await channel
93+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
94+
95+
var notifications = StreamQueue(channel.notifications);
96+
var notification = await notifications.next;
97+
var params = protocol.AnalysisErrorsParams.fromNotification(notification);
98+
expect(params.file, convertPath('/package1/lib/test.dart'));
99+
expect(params.errors, isEmpty);
100+
101+
await channel.sendRequest(protocol.AnalysisUpdateContentParams(
102+
{filePath: protocol.AddContentOverlay('int b = 0;')}));
103+
104+
notification = await notifications.next;
105+
params = protocol.AnalysisErrorsParams.fromNotification(notification);
106+
expect(params.file, convertPath('/package1/lib/test.dart'));
107+
expect(params.errors, isEmpty);
108+
109+
await channel.sendRequest(protocol.AnalysisUpdateContentParams({
110+
filePath: protocol.ChangeContentOverlay(
111+
[protocol.SourceEdit(0, 9, 'bool b = false')])
112+
}));
113+
114+
notification = await notifications.next;
115+
params = protocol.AnalysisErrorsParams.fromNotification(notification);
116+
expect(params.file, convertPath('/package1/lib/test.dart'));
117+
expect(params.errors, hasLength(1));
118+
_expectAnalysisError(params.errors.single, message: 'No bools message');
119+
}
120+
121+
Future<void> test_updateContent_removeOverlay() async {
122+
newFile(filePath, 'bool b = false;');
123+
await channel
124+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
125+
126+
var notifications = StreamQueue(channel.notifications);
127+
var notification = await notifications.next;
128+
var params = protocol.AnalysisErrorsParams.fromNotification(notification);
129+
expect(params.file, convertPath('/package1/lib/test.dart'));
130+
expect(params.errors, hasLength(1));
131+
_expectAnalysisError(params.errors.single, message: 'No bools message');
132+
133+
await channel.sendRequest(protocol.AnalysisUpdateContentParams(
134+
{filePath: protocol.AddContentOverlay('int b = 7;')}));
135+
136+
notification = await notifications.next;
137+
params = protocol.AnalysisErrorsParams.fromNotification(notification);
138+
expect(params.file, convertPath('/package1/lib/test.dart'));
139+
expect(params.errors, isEmpty);
140+
141+
await channel.sendRequest(protocol.AnalysisUpdateContentParams(
142+
{filePath: protocol.RemoveContentOverlay()}));
143+
144+
notification = await notifications.next;
145+
params = protocol.AnalysisErrorsParams.fromNotification(notification);
146+
expect(params.file, convertPath('/package1/lib/test.dart'));
147+
expect(params.errors, hasLength(1));
148+
_expectAnalysisError(params.errors.single, message: 'No bools message');
149+
}
150+
151+
void _expectAnalysisError(protocol.AnalysisError error,
152+
{required String message}) {
153+
expect(
154+
error,
155+
isA<protocol.AnalysisError>()
156+
.having((e) => e.severity, 'severity',
157+
protocol.AnalysisErrorSeverity.INFO)
158+
.having(
159+
(e) => e.type, 'type', protocol.AnalysisErrorType.STATIC_WARNING)
160+
.having((e) => e.message, 'message', message),
161+
);
162+
}
76163
}
77164

78165
class _NoBoolsPlugin extends Plugin {

0 commit comments

Comments
 (0)