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(); +}