From ca31648dbb9ee9a5707588bf665bd60ce2ef71a3 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 31 Jan 2025 10:44:36 -0800 Subject: [PATCH] Support 'new' analyzer plugins in `dart analyze`. This change allows the LegacyAnalysisServer to understand when the plugin isolate (if there is one) is analyzing or not. There are a few primary concepts: * The plugin isolate (PluginServer) notifies the analysis server, when analyzing all files in a context collection, and analyzing changed files, that it is analyzing, and later that it isn't. * The NotificationManager tracks whether the plugin isolate is analyzing or not, based on the last status. * The PluginManager tracks whether new plugins are initialized or not. This is determined by the work done by the PluginWatcher. If no plugins are configured, then plugins are declared to be "initialized". Otherwise, the AnalysisServer sets their status to be "initialized" after receiving the first status notification from the plugin isolate. * The LegacyAnalysisServer now uses the additional "are plugins analyzing" signal, held in NotificationManager, and the "are plugins initializing" signal, held in PluginManager, to determine whether to notify the client that analysis is complete. Change-Id: Ie2b6a6048f074d7a26d7d5d07622a17c30fcab96 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405444 Reviewed-by: Brian Wilkerson --- .../lib/src/legacy_analysis_server.dart | 123 +++++++++++++----- .../lib/src/lsp/lsp_analysis_server.dart | 4 + .../lib/src/plugin/notification_manager.dart | 35 +++++ .../lib/src/plugin/plugin_manager.dart | 15 ++- .../lib/src/plugin/plugin_watcher.dart | 92 ++++++++----- .../lib/src/utilities/mocks.dart | 3 + .../test/src/plugin/plugin_watcher_test.dart | 3 + .../lib/src/plugin_server.dart | 12 ++ .../test/src/plugin_server_error_test.dart | 5 + .../test/src/plugin_server_test.dart | 40 +++--- .../test/src/plugin_server_test_base.dart | 56 ++++---- 11 files changed, 266 insertions(+), 122 deletions(-) diff --git a/pkg/analysis_server/lib/src/legacy_analysis_server.dart b/pkg/analysis_server/lib/src/legacy_analysis_server.dart index b4d0b0a588b..3ecb9b0d75a 100644 --- a/pkg/analysis_server/lib/src/legacy_analysis_server.dart +++ b/pkg/analysis_server/lib/src/legacy_analysis_server.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +/// @docImport 'package:analysis_server_plugin/src/plugin_server.dart'; +library; + import 'dart:async'; import 'dart:io' as io; import 'dart:math' show max; @@ -267,10 +270,15 @@ class LegacyAnalysisServer extends AnalysisServer { late final FutureOr lspInitialized = InitializedStateMessageHandler(this); - /// A flag indicating the value of the 'analyzing' parameter sent in the last - /// status message to the client. + /// Whether either the last status message sent to the client or the last + /// status message sent from any [PluginServer] indicated `isWorking: true`. + @visibleForTesting bool statusAnalyzing = false; + /// Whether the analysis server is currently analyzing (not including any + /// plugins). + bool serverStatusAnalyzing = false; + /// A set of the [ServerService]s to send notifications for. Set serverServices = {}; @@ -321,6 +329,9 @@ class LegacyAnalysisServer extends AnalysisServer { int nextSearchId = 0; /// The [Completer] that completes when analysis is complete. + /// + /// This Completer is not used for communicating to the client whether we are + /// analyzing; it is only used by some 'search_find' handlers, and in some tests. Completer? _onAnalysisCompleteCompleter; /// The controller that is notified when analysis is started. @@ -423,6 +434,30 @@ class LegacyAnalysisServer extends AnalysisServer { discardedRequests, ).listen(handleRequestOrResponse, onDone: done, onError: error); _newRefactoringManager(); + + pluginManager.initializedCompleter.future.then((_) { + // Perform "on idle" tasks in case the `pluginManger` determines that no + // plugins should be run, _after_ the analysis server has reported its + // final `isAnalyzing: false` status. + _performOnIdleActions( + // Use the existing plugin analyzing status. + isPluginAnalyzing: notificationManager.pluginStatusAnalyzing, + ); + }); + + notificationManager.pluginAnalysisStatusChanges.listen(( + pluginStatusAnalyzing, + ) { + if (!pluginManager.initializedCompleter.isCompleted) { + // Without `this.`, some portion of the analyzer believes we are accessing + // the super parameter, instead of the field in the super class. + // See https://github.com/dart-lang/sdk/issues/59996. + // ignore: unnecessary_this + this.pluginManager.initializedCompleter.complete(); + } else { + _performOnIdleActions(isPluginAnalyzing: pluginStatusAnalyzing); + } + }); } /// The most recently registered set of client capabilities. The default is to @@ -468,6 +503,10 @@ class LegacyAnalysisServer extends AnalysisServer { _editorClientCapabilities; /// The [Future] that completes when analysis is complete. + /// + /// This Future is not used for communicating to the client whether we are + /// analyzing; it is only used by 'search_find' handlers, tests, and for + /// performance calculations. Future get onAnalysisComplete { if (_isAnalysisComplete) { return Future.value(); @@ -526,6 +565,7 @@ class LegacyAnalysisServer extends AnalysisServer { bool get supportsShowMessageRequest => clientCapabilities.requests.contains('showMessageRequest'); + // TODO(srawlins): Do we need to alter this to account for plugin status? bool get _isAnalysisComplete => !analysisDriverScheduler.isWorking; void cancelRequest(String id) { @@ -817,43 +857,20 @@ class LegacyAnalysisServer extends AnalysisServer { /// Send status notification to the client. The state of analysis is given by /// the [status] information. void sendStatusNotificationNew(analysis.AnalysisStatus status) { - var isAnalyzing = status.isWorking; - if (isAnalyzing) { + var isServerAnalyzing = status.isWorking; + if (isServerAnalyzing) { _onAnalysisStartedController.add(true); } var onAnalysisCompleteCompleter = _onAnalysisCompleteCompleter; - if (onAnalysisCompleteCompleter != null && !isAnalyzing) { + if (onAnalysisCompleteCompleter != null && !isServerAnalyzing) { onAnalysisCompleteCompleter.complete(); _onAnalysisCompleteCompleter = null; } - // Perform on-idle actions. - if (!isAnalyzing) { - if (generalAnalysisServices.contains( - GeneralAnalysisService.ANALYZED_FILES, - )) { - sendAnalysisNotificationAnalyzedFiles(this); - } - _scheduleAnalysisImplementedNotification(); - filesResolvedSinceLastIdle.clear(); - } - // Only send status when subscribed. - if (!serverServices.contains(ServerService.STATUS)) { - return; - } - // Only send status when it changes - if (statusAnalyzing == isAnalyzing) { - return; - } - statusAnalyzing = isAnalyzing; - if (!isAnalyzing) { - // Only send analysis analytics after analysis is complete. - reportAnalysisAnalytics(); - } - var analysis = AnalysisStatus(isAnalyzing); - channel.sendNotification( - ServerStatusParams( - analysis: analysis, - ).toNotification(clientUriConverter: uriConverter), + serverStatusAnalyzing = isServerAnalyzing; + + _performOnIdleActions( + // Use the existing plugin analyzing status. + isPluginAnalyzing: notificationManager.pluginStatusAnalyzing, ); } @@ -1099,6 +1116,46 @@ class LegacyAnalysisServer extends AnalysisServer { _refactoringManager = RefactoringManager(this, refactoringWorkspace); } + /// Performs "on idle" actions, given either a new status for whether the + /// server is analyzing, or a new status for whether the plugin isolate is + /// analyzing. + void _performOnIdleActions({required bool isPluginAnalyzing}) { + // Perform on-idle actions. + var isAnalyzing = + serverStatusAnalyzing || + isPluginAnalyzing || + !pluginManager.initializedCompleter.isCompleted; + if (!serverStatusAnalyzing) { + if (generalAnalysisServices.contains( + GeneralAnalysisService.ANALYZED_FILES, + )) { + sendAnalysisNotificationAnalyzedFiles(this); + } + _scheduleAnalysisImplementedNotification(); + filesResolvedSinceLastIdle.clear(); + } + // Only send status when subscribed. + if (!serverServices.contains(ServerService.STATUS)) { + return; + } + + // Only send status when it changes. + if (statusAnalyzing == isAnalyzing) { + return; + } + statusAnalyzing = isAnalyzing; + if (!serverStatusAnalyzing) { + // Only send analysis analytics after analysis is complete. + reportAnalysisAnalytics(); + } + var analysis = AnalysisStatus(isAnalyzing); + channel.sendNotification( + ServerStatusParams( + analysis: analysis, + ).toNotification(clientUriConverter: uriConverter), + ); + } + void _scheduleAnalysisImplementedNotification() { var subscribed = analysisServices[AnalysisService.IMPLEMENTED]; if (subscribed == null) { diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart index 9357d9fa2a0..90c660baf45 100644 --- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart +++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart @@ -201,6 +201,10 @@ class LspAnalysisServer extends AnalysisServer { _pluginChangeSubscription = pluginManager.pluginsChanged.listen( (_) => _onPluginsChanged(), ); + + // TODO(srawlins): Listen to + // `notificationManager.pluginAnalysisStatusChanges` and perform "on idle" + // tasks. } } diff --git a/pkg/analysis_server/lib/src/plugin/notification_manager.dart b/pkg/analysis_server/lib/src/plugin/notification_manager.dart index d2d85cf044c..3c20b8fe7f0 100644 --- a/pkg/analysis_server/lib/src/plugin/notification_manager.dart +++ b/pkg/analysis_server/lib/src/plugin/notification_manager.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:async'; import 'dart:collection'; import 'package:analysis_server/protocol/protocol_generated.dart' as server; @@ -71,6 +72,14 @@ abstract class AbstractNotificationManager { /// The object used to merge results. final ResultMerger merger = ResultMerger(); + /// Whether the plugin isolate is currently analyzing, as per its last status + /// notification. + bool pluginStatusAnalyzing = false; + + /// The controller that is notified when analysis status changes. + final StreamController _analysisStatusChangesController = + StreamController.broadcast(); + /// Initialize a newly created notification manager. AbstractNotificationManager(this._pathContext) : folding = ResultCollector>(serverId), @@ -79,6 +88,13 @@ abstract class AbstractNotificationManager { _occurrences = ResultCollector>(serverId), _outlines = ResultCollector>(serverId); + /// The Stream of analysis statuses from the plugin isolate. + /// + /// Each value emitted represents whether the plugin isolate is analyzing or + /// not, as per each status notification. + Stream get pluginAnalysisStatusChanges => + _analysisStatusChangesController.stream; + /// Handle the given [notification] from the plugin with the given [pluginId]. void handlePluginNotification( String pluginId, @@ -120,6 +136,8 @@ abstract class AbstractNotificationManager { recordOutlines(pluginId, params.file, params.outline); case plugin.PLUGIN_NOTIFICATION_ERROR: sendPluginErrorNotification(notification); + case plugin.PLUGIN_NOTIFICATION_STATUS: + _setPluginStatus(notification); } } @@ -335,6 +353,23 @@ abstract class AbstractNotificationManager { // disabled. return isIncluded() && !isExcluded(); } + + /// Records a status notification from the analyzer plugin. + void _setPluginStatus(plugin.Notification notification) { + var params = plugin.PluginStatusParams.fromNotification(notification); + var analysis = params.analysis; + if (analysis == null) { + return; + } + var isAnalyzing = analysis.isAnalyzing; + _analysisStatusChangesController.add(isAnalyzing); + + // Only send status when it changes. + if (pluginStatusAnalyzing == isAnalyzing) { + return; + } + pluginStatusAnalyzing = isAnalyzing; + } } class NotificationManager extends AbstractNotificationManager { diff --git a/pkg/analysis_server/lib/src/plugin/plugin_manager.dart b/pkg/analysis_server/lib/src/plugin/plugin_manager.dart index 96d81ca4689..ea8b39c9e7f 100644 --- a/pkg/analysis_server/lib/src/plugin/plugin_manager.dart +++ b/pkg/analysis_server/lib/src/plugin/plugin_manager.dart @@ -2,6 +2,10 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +/// @docImport 'package:analysis_server_plugin/src/plugin_server.dart'; +/// @docImport 'package:analysis_server/src/plugin/plugin_watcher.dart'; +library; + import 'dart:async'; import 'dart:collection'; import 'dart:convert'; @@ -320,6 +324,14 @@ class PluginManager { final StreamController _pluginsChanged = StreamController.broadcast(); + /// Whether plugins are "initialized." + /// + /// Plugins are declared to be initialized either (a) when the [PluginWatcher] + /// has determined no plugins are configured to be run, or (b) when the + /// plugins are configured and the first status notification is received by + /// the analysis server. + Completer initializedCompleter = Completer(); + /// Initialize a newly created plugin manager. The notifications from the /// running plugins will be handled by the given [notificationManager]. PluginManager( @@ -376,6 +388,7 @@ class PluginManager { ); _pluginMap[path] = plugin; try { + instrumentationService.logInfo('Starting plugin "$plugin"'); var session = await plugin.start(byteStorePath, sdkPath); unawaited( session?.onDone.then((_) { @@ -948,7 +961,7 @@ class PluginSession { /// Return a future that will complete when the plugin has stopped. Future get onDone => pluginStoppedCompleter.future; - /// Handle the given [notification]. + /// Handle the given [notification] from [PluginServer]. void handleNotification(Notification notification) { if (notification.event == PLUGIN_NOTIFICATION_ERROR) { var params = PluginErrorParams.fromNotification(notification); diff --git a/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart b/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart index cdf18b36450..595a879af9d 100644 --- a/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart +++ b/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart @@ -46,7 +46,35 @@ class PluginWatcher implements DriverWatcher { // We temporarily support both "legacy plugins" and (new) "plugins." We // restrict the number of legacy plugins to 1, for performance reasons. // At some point, we will stop adding legacy plugins to the context root. + _addLegacyPlugins(driver); + if (driver.pluginConfigurations.isEmpty) { + // Call the plugin manager "initialized." + if (!manager.initializedCompleter.isCompleted) { + manager.initializedCompleter.complete(); + } + return; + } + + // Now we add any specified (new) plugins to the context, as a single + // "legacy plugin" shared entrypoint. + // Add a shared entrypoint plugin to the context root, only if one or more + // plugins are specified in analysis options. + _addPlugins(driver); + } + + /// The context manager has just removed the given analysis [driver]. + @override + void removedDriver(AnalysisDriver driver) { + var info = _driverInfo[driver]; + if (info == null) { + throw StateError('Cannot remove a driver that was not added'); + } + manager.removedContextRoot(driver.analysisContext!.contextRoot); + _driverInfo.remove(driver); + } + + void _addLegacyPlugins(AnalysisDriver driver) { for (var hostPackageName in driver.enabledLegacyPluginNames) { // // Determine whether the package exists and defines a plugin. @@ -74,44 +102,36 @@ class PluginWatcher implements DriverWatcher { isLegacyPlugin: true, ); } - - // Now we add any specified (new) plugins to the context, as a single - // "legacy plugin" shared entrypoint. - var pluginConfigurations = driver.pluginConfigurations; - // Add a shared entrypoint plugin to the context root, only if one or more - // plugins are specified in analysis options. - if (pluginConfigurations.isNotEmpty) { - var contextRoot = driver.analysisContext!.contextRoot; - var packageGenerator = PluginPackageGenerator(pluginConfigurations); - // The path here just needs to be unique per context root. - var sharedPluginFolder = manager.pluginStateFolder(contextRoot.root.path) - ..create(); - sharedPluginFolder - .getChildAssumingFile(file_paths.pubspecYaml) - .writeAsStringSync(packageGenerator.generatePubspec()); - var libFolder = sharedPluginFolder.getChildAssumingFolder('bin') - ..create(); - libFolder - .getChildAssumingFile('plugin.dart') - .writeAsStringSync(packageGenerator.generateEntrypoint()); - - manager.addPluginToContextRoot( - contextRoot, - sharedPluginFolder.path, - isLegacyPlugin: false, - ); - } } - /// The context manager has just removed the given analysis [driver]. - @override - void removedDriver(AnalysisDriver driver) { - var info = _driverInfo[driver]; - if (info == null) { - throw StateError('Cannot remove a driver that was not added'); - } - manager.removedContextRoot(driver.analysisContext!.contextRoot); - _driverInfo.remove(driver); + void _addPlugins(AnalysisDriver driver) { + var pluginConfigurations = driver.pluginConfigurations; + var contextRoot = driver.analysisContext!.contextRoot; + var packageGenerator = PluginPackageGenerator(pluginConfigurations); + // The path here just needs to be unique per context root. + + var sharedPluginFolder = manager.pluginStateFolder(contextRoot.root.path); + manager.instrumentationService.logInfo( + "Creating shared plugin folder at '${sharedPluginFolder.path}' for " + "context root: '${contextRoot.root.path}'", + ); + sharedPluginFolder.create(); + sharedPluginFolder + .getChildAssumingFile(file_paths.pubspecYaml) + .writeAsStringSync(packageGenerator.generatePubspec()); + var binFolder = sharedPluginFolder.getChildAssumingFolder('bin')..create(); + binFolder + .getChildAssumingFile('plugin.dart') + .writeAsStringSync(packageGenerator.generateEntrypoint()); + manager.instrumentationService.logInfo( + 'Adding ${driver.pluginConfigurations.length} analyzer plugins for ' + "context root: '${contextRoot.root.path}'", + ); + manager.addPluginToContextRoot( + contextRoot, + sharedPluginFolder.path, + isLegacyPlugin: false, + ); } /// Return the path to the root of the SDK being used by the given analysis diff --git a/pkg/analysis_server/lib/src/utilities/mocks.dart b/pkg/analysis_server/lib/src/utilities/mocks.dart index db9e1f45a05..19a3f1f3b48 100644 --- a/pkg/analysis_server/lib/src/utilities/mocks.dart +++ b/pkg/analysis_server/lib/src/utilities/mocks.dart @@ -231,6 +231,9 @@ class TestPluginManager implements PluginManager { @override List plugins = []; + @override + Completer initializedCompleter = Completer(); + StreamController pluginsChangedController = StreamController.broadcast(); diff --git a/pkg/analysis_server/test/src/plugin/plugin_watcher_test.dart b/pkg/analysis_server/test/src/plugin/plugin_watcher_test.dart index 41902fc23ed..b2470aaa00f 100644 --- a/pkg/analysis_server/test/src/plugin/plugin_watcher_test.dart +++ b/pkg/analysis_server/test/src/plugin/plugin_watcher_test.dart @@ -106,6 +106,9 @@ class TestPluginManager implements PluginManager { List removedContextRoots = []; + @override + Completer initializedCompleter = Completer(); + @override Future addPluginToContextRoot( ContextRoot contextRoot, diff --git a/pkg/analysis_server_plugin/lib/src/plugin_server.dart b/pkg/analysis_server_plugin/lib/src/plugin_server.dart index 25bbe404dc5..dc4032bd586 100644 --- a/pkg/analysis_server_plugin/lib/src/plugin_server.dart +++ b/pkg/analysis_server_plugin/lib/src/plugin_server.dart @@ -189,6 +189,9 @@ class PluginServer { Future _analyzeAllFilesInContextCollection({ required AnalysisContextCollection contextCollection, }) async { + _channel.sendNotification( + protocol.PluginStatusParams(analysis: protocol.AnalysisStatus(true)) + .toNotification()); await _forAnalysisContexts(contextCollection, (analysisContext) async { var paths = analysisContext.contextRoot .analyzedFiles() @@ -203,6 +206,9 @@ class PluginServer { paths: paths, ); }); + _channel.sendNotification( + protocol.PluginStatusParams(analysis: protocol.AnalysisStatus(false)) + .toNotification()); } Future _analyzeFile({ @@ -496,6 +502,9 @@ class PluginServer { /// Handles the fact that files with [paths] were changed. Future _handleContentChanged(List paths) async { if (_contextCollection case var contextCollection?) { + _channel.sendNotification( + protocol.PluginStatusParams(analysis: protocol.AnalysisStatus(true)) + .toNotification()); await _forAnalysisContexts(contextCollection, (analysisContext) async { for (var path in paths) { analysisContext.changeFile(path); @@ -504,6 +513,9 @@ class PluginServer { await _handleAffectedFiles( analysisContext: analysisContext, paths: affected); }); + _channel.sendNotification( + protocol.PluginStatusParams(analysis: protocol.AnalysisStatus(false)) + .toNotification()); } } diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart b/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart index d3d9d883157..c8ed37103cc 100644 --- a/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart +++ b/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart @@ -12,6 +12,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/src/lint/linter.dart'; +import 'package:analyzer_plugin/protocol/protocol_constants.dart' as protocol; import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; @@ -60,12 +61,14 @@ plugins: // StreamQueues listening. var notifications = channel.notifications.asBroadcastStream(); var analysisErrorsParamsQueue = StreamQueue(notifications + .where((n) => n.event == protocol.ANALYSIS_NOTIFICATION_ERRORS) .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) .where((p) => p.file == filePath)); var analysisErrorsParams = await analysisErrorsParamsQueue.next; expect(analysisErrorsParams.errors, isEmpty); var pluginErrorParamsQueue = StreamQueue(notifications + .where((n) => n.event == protocol.PLUGIN_NOTIFICATION_ERROR) .map((n) => protocol.PluginErrorParams.fromNotification(n))); var pluginErrorParams = await pluginErrorParamsQueue.next; expect(pluginErrorParams.isFatal, false); @@ -116,12 +119,14 @@ plugins: // StreamQueues listening. var notifications = channel.notifications.asBroadcastStream(); var analysisErrorsParamsQueue = StreamQueue(notifications + .where((n) => n.event == protocol.ANALYSIS_NOTIFICATION_ERRORS) .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) .where((p) => p.file == filePath)); var analysisErrorsParams = await analysisErrorsParamsQueue.next; expect(analysisErrorsParams.errors.single, isNotNull); var pluginErrorParamsQueue = StreamQueue(notifications + .where((n) => n.event == protocol.PLUGIN_NOTIFICATION_ERROR) .map((n) => protocol.PluginErrorParams.fromNotification(n))); var pluginErrorParams = await pluginErrorParamsQueue.next; expect(pluginErrorParams.isFatal, false); diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_test.dart b/pkg/analysis_server_plugin/test/src/plugin_server_test.dart index af6e4c8319e..61a7f7d88d0 100644 --- a/pkg/analysis_server_plugin/test/src/plugin_server_test.dart +++ b/pkg/analysis_server_plugin/test/src/plugin_server_test.dart @@ -9,6 +9,7 @@ import 'package:analysis_server_plugin/plugin.dart'; import 'package:analysis_server_plugin/registry.dart'; import 'package:analysis_server_plugin/src/plugin_server.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol; +import 'package:analyzer_plugin/protocol/protocol_constants.dart' as protocol; import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; @@ -31,6 +32,13 @@ class PluginServerTest extends PluginServerTestBase { String get packagePath => convertPath('/package1'); + StreamQueue get _analysisErrorsParams { + return StreamQueue(channel.notifications + .where((n) => n.event == protocol.ANALYSIS_NOTIFICATION_ERRORS) + .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) + .where((p) => p.file == filePath)); + } + @override Future setUp() async { await super.setUp(); @@ -45,9 +53,7 @@ class PluginServerTest extends PluginServerTestBase { newFile(filePath, 'bool b = false;'); await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, hasLength(1)); _expectAnalysisError(params.errors.single, message: 'No bools message'); @@ -74,9 +80,7 @@ class PluginServerTest extends PluginServerTestBase { newFile(filePath, 'double x = 3.14;'); await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, isEmpty); } @@ -86,9 +90,7 @@ class PluginServerTest extends PluginServerTestBase { newFile(filePath, 'double x = 3.14;'); await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, hasLength(1)); _expectAnalysisError(params.errors.single, message: 'No doubles message'); @@ -100,9 +102,7 @@ class PluginServerTest extends PluginServerTestBase { await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, isEmpty); @@ -120,9 +120,7 @@ class PluginServerTest extends PluginServerTestBase { await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, isEmpty); @@ -148,9 +146,7 @@ class PluginServerTest extends PluginServerTestBase { await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, hasLength(1)); _expectAnalysisError(params.errors.single, message: 'No bools message'); @@ -174,9 +170,7 @@ class PluginServerTest extends PluginServerTestBase { newFile(filePath, 'bool b = false;'); await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, hasLength(1)); _expectAnalysisError(params.errors.single, message: 'No bools message'); @@ -187,9 +181,7 @@ class PluginServerTest extends PluginServerTestBase { newFile(filePath, 'bool b = false;'); await channel .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot])); - var paramsQueue = StreamQueue(channel.notifications - .map((n) => protocol.AnalysisErrorsParams.fromNotification(n)) - .where((p) => p.file == filePath)); + var paramsQueue = _analysisErrorsParams; var params = await paramsQueue.next; expect(params.errors, isEmpty); } diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart b/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart index a42d919f27e..e097dc9fadd 100644 --- a/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart +++ b/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart @@ -17,34 +17,7 @@ import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' import 'package:meta/meta.dart'; import 'package:test/test.dart'; -class PluginServerTestBase with ResourceProviderMixin { - final channel = _FakeChannel(); - - late final PluginServer pluginServer; - - Folder get byteStoreRoot => getFolder('/byteStore'); - - Folder get sdkRoot => getFolder('/sdk'); - - @mustCallSuper - Future setUp() async { - createMockSdk(resourceProvider: resourceProvider, root: sdkRoot); - } - - Future startPlugin() async { - await pluginServer.initialize(); - pluginServer.start(channel); - - await pluginServer.handlePluginVersionCheck( - protocol.PluginVersionCheckParams( - byteStoreRoot.path, sdkRoot.path, '0.0.1'), - ); - } - - void tearDown() => registeredFixGenerators.clearLintProducers(); -} - -class _FakeChannel implements PluginCommunicationChannel { +class FakeChannel implements PluginCommunicationChannel { final _completers = >{}; final StreamController _notificationsController = @@ -90,3 +63,30 @@ class _FakeChannel implements PluginCommunicationChannel { completer?.complete(response); } } + +class PluginServerTestBase with ResourceProviderMixin { + final channel = FakeChannel(); + + late final PluginServer pluginServer; + + Folder get byteStoreRoot => getFolder('/byteStore'); + + Folder get sdkRoot => getFolder('/sdk'); + + @mustCallSuper + Future setUp() async { + createMockSdk(resourceProvider: resourceProvider, root: sdkRoot); + } + + Future startPlugin() async { + await pluginServer.initialize(); + pluginServer.start(channel); + + await pluginServer.handlePluginVersionCheck( + protocol.PluginVersionCheckParams( + byteStoreRoot.path, sdkRoot.path, '0.0.1'), + ); + } + + void tearDown() => registeredFixGenerators.clearLintProducers(); +}