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 <brianwilkerson@google.com>
This commit is contained in:
Sam Rawlins
2025-01-31 10:44:36 -08:00
committed by Samuel Rawlins
parent f5d01eb734
commit ca31648dbb
11 changed files with 266 additions and 122 deletions
@@ -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<InitializedStateMessageHandler> 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<ServerService> 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<void>? _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<void> 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) {
@@ -201,6 +201,10 @@ class LspAnalysisServer extends AnalysisServer {
_pluginChangeSubscription = pluginManager.pluginsChanged.listen(
(_) => _onPluginsChanged(),
);
// TODO(srawlins): Listen to
// `notificationManager.pluginAnalysisStatusChanges` and perform "on idle"
// tasks.
}
}
@@ -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<bool> _analysisStatusChangesController =
StreamController.broadcast();
/// Initialize a newly created notification manager.
AbstractNotificationManager(this._pathContext)
: folding = ResultCollector<List<FoldingRegion>>(serverId),
@@ -79,6 +88,13 @@ abstract class AbstractNotificationManager {
_occurrences = ResultCollector<List<Occurrences>>(serverId),
_outlines = ResultCollector<List<Outline>>(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<bool> 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 {
@@ -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<void> _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<void> 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<void> 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);
@@ -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
@@ -231,6 +231,9 @@ class TestPluginManager implements PluginManager {
@override
List<PluginInfo> plugins = [];
@override
Completer<void> initializedCompleter = Completer();
StreamController<void> pluginsChangedController =
StreamController.broadcast();
@@ -106,6 +106,9 @@ class TestPluginManager implements PluginManager {
List<ContextRoot> removedContextRoots = <ContextRoot>[];
@override
Completer<void> initializedCompleter = Completer();
@override
Future<void> addPluginToContextRoot(
ContextRoot contextRoot,
@@ -189,6 +189,9 @@ class PluginServer {
Future<void> _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<void> _analyzeFile({
@@ -496,6 +502,9 @@ class PluginServer {
/// Handles the fact that files with [paths] were changed.
Future<void> _handleContentChanged(List<String> 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());
}
}
@@ -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);
@@ -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<protocol.AnalysisErrorsParams> 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<void> 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);
}
@@ -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<void> setUp() async {
createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
}
Future<void> 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 = <String, Completer<protocol.Response>>{};
final StreamController<protocol.Notification> _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<void> setUp() async {
createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
}
Future<void> startPlugin() async {
await pluginServer.initialize();
pluginServer.start(channel);
await pluginServer.handlePluginVersionCheck(
protocol.PluginVersionCheckParams(
byteStoreRoot.path, sdkRoot.path, '0.0.1'),
);
}
void tearDown() => registeredFixGenerators.clearLintProducers();
}