blob: 1b7daf5f1f388898d5c0f9e9b6d599f2adb37cbe [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// 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:io' as io;
import 'dart:math' show max;
import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp;
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart'
hide AnalysisOptions, MessageType;
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/analytics/analytics_manager.dart';
import 'package:analysis_server/src/channel/channel.dart';
import 'package:analysis_server/src/computer/computer_highlights.dart';
import 'package:analysis_server/src/domains/analysis/occurrences.dart';
import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart';
import 'package:analysis_server/src/flutter/flutter_notifications.dart';
import 'package:analysis_server/src/handler/legacy/analysis_get_errors.dart';
import 'package:analysis_server/src/handler/legacy/analysis_get_hover.dart';
import 'package:analysis_server/src/handler/legacy/analysis_get_imported_elements.dart';
import 'package:analysis_server/src/handler/legacy/analysis_get_navigation.dart';
import 'package:analysis_server/src/handler/legacy/analysis_get_signature.dart';
import 'package:analysis_server/src/handler/legacy/analysis_reanalyze.dart';
import 'package:analysis_server/src/handler/legacy/analysis_set_analysis_roots.dart';
import 'package:analysis_server/src/handler/legacy/analysis_set_general_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/analysis_set_priority_files.dart';
import 'package:analysis_server/src/handler/legacy/analysis_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/analysis_update_content.dart';
import 'package:analysis_server/src/handler/legacy/analysis_update_options.dart';
import 'package:analysis_server/src/handler/legacy/analytics_enable.dart';
import 'package:analysis_server/src/handler/legacy/analytics_is_enabled.dart';
import 'package:analysis_server/src/handler/legacy/analytics_send_event.dart';
import 'package:analysis_server/src/handler/legacy/analytics_send_timing.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestion_details2.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestions2.dart';
import 'package:analysis_server/src/handler/legacy/diagnostic_get_diagnostics.dart';
import 'package:analysis_server/src/handler/legacy/diagnostic_get_server_port.dart';
import 'package:analysis_server/src/handler/legacy/edit_bulk_fixes.dart';
import 'package:analysis_server/src/handler/legacy/edit_format.dart';
import 'package:analysis_server/src/handler/legacy/edit_format_if_enabled.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_assists.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_available_refactorings.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_fixes.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_postfix_completion.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_refactoring.dart';
import 'package:analysis_server/src/handler/legacy/edit_get_statement_completion.dart';
import 'package:analysis_server/src/handler/legacy/edit_import_elements.dart';
import 'package:analysis_server/src/handler/legacy/edit_is_postfix_completion_applicable.dart';
import 'package:analysis_server/src/handler/legacy/edit_list_postfix_completion_templates.dart';
import 'package:analysis_server/src/handler/legacy/edit_organize_directives.dart';
import 'package:analysis_server/src/handler/legacy/edit_sort_members.dart';
import 'package:analysis_server/src/handler/legacy/execution_create_context.dart';
import 'package:analysis_server/src/handler/legacy/execution_delete_context.dart';
import 'package:analysis_server/src/handler/legacy/execution_get_suggestions.dart';
import 'package:analysis_server/src/handler/legacy/execution_map_uri.dart';
import 'package:analysis_server/src/handler/legacy/execution_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/flutter_get_widget_description.dart';
import 'package:analysis_server/src/handler/legacy/flutter_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/flutter_set_widget_property_value.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
import 'package:analysis_server/src/handler/legacy/lsp_over_legacy_handler.dart';
import 'package:analysis_server/src/handler/legacy/search_find_element_references.dart';
import 'package:analysis_server/src/handler/legacy/search_find_member_declarations.dart';
import 'package:analysis_server/src/handler/legacy/search_find_member_references.dart';
import 'package:analysis_server/src/handler/legacy/search_find_top_level_declarations.dart';
import 'package:analysis_server/src/handler/legacy/search_get_element_declarations.dart';
import 'package:analysis_server/src/handler/legacy/search_get_type_hierarchy.dart';
import 'package:analysis_server/src/handler/legacy/server_cancel_request.dart';
import 'package:analysis_server/src/handler/legacy/server_get_version.dart';
import 'package:analysis_server/src/handler/legacy/server_set_client_capabilities.dart';
import 'package:analysis_server/src/handler/legacy/server_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/server_shutdown.dart';
import 'package:analysis_server/src/handler/legacy/unsupported_request.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart' as lsp;
import 'package:analysis_server/src/lsp/client_configuration.dart' as lsp;
import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
import 'package:analysis_server/src/operation/operation_analysis.dart';
import 'package:analysis_server/src/plugin/notification_manager.dart';
import 'package:analysis_server/src/protocol_server.dart' as server;
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/server/debounce_requests.dart';
import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/server/error_notifier.dart';
import 'package:analysis_server/src/server/features.dart';
import 'package:analysis_server/src/server/message_scheduler.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/server/sdk_configuration.dart';
import 'package:analysis_server/src/services/completion/completion_state.dart';
import 'package:analysis_server/src/services/execution/execution_context.dart';
import 'package:analysis_server/src/services/flutter/widget_descriptions.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring_manager.dart';
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/utilities/process.dart';
import 'package:analysis_server/src/utilities/request_statistics.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/dart/analysis/status.dart' as analysis;
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/cancellation.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:telemetry/crash_reporting.dart';
import 'package:watcher/watcher.dart';
/// A function that can be executed to create a handler for a request.
typedef HandlerGenerator = LegacyHandler Function(
LegacyAnalysisServer, Request, CancellationToken, OperationPerformanceImpl);
typedef OptionUpdater = void Function(AnalysisOptionsImpl options);
/// Various IDE options.
class AnalysisServerOptions {
String? newAnalysisDriverLog;
String? clientId;
String? clientVersion;
/// Base path where to cache data.
String? cacheFolder;
/// The path to the package config file override.
/// If `null`, then the default discovery mechanism is used.
String? packagesFile;
/// The crash report sender instance; note, this object can be `null`, and
/// should be accessed via a null-aware operator.
CrashReportSender? crashReportSender;
/// An optional set of configuration overrides specified by the SDK.
///
/// These overrides can provide new values for configuration settings, and are
/// generally used in specific SDKs (like the internal google3 one).
SdkConfiguration? configurationOverrides;
/// Whether to use the Language Server Protocol.
bool useLanguageServerProtocol = false;
/// The set of enabled features.
FeatureSet featureSet = FeatureSet();
/// If set, this string will be reported as the protocol version.
String? reportProtocolVersion;
/// Experiments which have been enabled (or disabled) via the
/// `--enable-experiment` command-line option.
List<String> enabledExperiments = [];
}
/// Instances of the class [LegacyAnalysisServer] implement a server that
/// listens on a [CommunicationChannel] for analysis requests and processes
/// them.
class LegacyAnalysisServer extends AnalysisServer {
/// A map from the name of a request to a function used to create a request
/// handler.
///
/// Requests that don't match anything in this map will be passed to
/// [LspOverLegacyHandler] for possible handling before returning an error.
static final Map<String, HandlerGenerator> requestHandlerGenerators = {
ANALYSIS_REQUEST_GET_ERRORS: AnalysisGetErrorsHandler.new,
ANALYSIS_REQUEST_GET_HOVER: AnalysisGetHoverHandler.new,
ANALYSIS_REQUEST_GET_IMPORTED_ELEMENTS:
AnalysisGetImportedElementsHandler.new,
ANALYSIS_REQUEST_GET_LIBRARY_DEPENDENCIES: UnsupportedRequestHandler.new,
ANALYSIS_REQUEST_GET_NAVIGATION: AnalysisGetNavigationHandler.new,
ANALYSIS_REQUEST_GET_REACHABLE_SOURCES: UnsupportedRequestHandler.new,
ANALYSIS_REQUEST_GET_SIGNATURE: AnalysisGetSignatureHandler.new,
ANALYSIS_REQUEST_REANALYZE: AnalysisReanalyzeHandler.new,
ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS: AnalysisSetAnalysisRootsHandler.new,
ANALYSIS_REQUEST_SET_GENERAL_SUBSCRIPTIONS:
AnalysisSetGeneralSubscriptionsHandler.new,
ANALYSIS_REQUEST_SET_PRIORITY_FILES: AnalysisSetPriorityFilesHandler.new,
ANALYSIS_REQUEST_SET_SUBSCRIPTIONS: AnalysisSetSubscriptionsHandler.new,
ANALYSIS_REQUEST_UPDATE_CONTENT: AnalysisUpdateContentHandler.new,
ANALYSIS_REQUEST_UPDATE_OPTIONS: AnalysisUpdateOptionsHandler.new,
//
ANALYTICS_REQUEST_IS_ENABLED: AnalyticsIsEnabledHandler.new,
ANALYTICS_REQUEST_ENABLE: AnalyticsEnableHandler.new,
ANALYTICS_REQUEST_SEND_EVENT: AnalyticsSendEventHandler.new,
ANALYTICS_REQUEST_SEND_TIMING: AnalyticsSendTimingHandler.new,
//
COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2:
CompletionGetSuggestionDetails2Handler.new,
COMPLETION_REQUEST_GET_SUGGESTIONS2: CompletionGetSuggestions2Handler.new,
//
DIAGNOSTIC_REQUEST_GET_DIAGNOSTICS: DiagnosticGetDiagnosticsHandler.new,
DIAGNOSTIC_REQUEST_GET_SERVER_PORT: DiagnosticGetServerPortHandler.new,
//
EDIT_REQUEST_FORMAT: EditFormatHandler.new,
EDIT_REQUEST_FORMAT_IF_ENABLED: EditFormatIfEnabledHandler.new,
EDIT_REQUEST_GET_ASSISTS: EditGetAssistsHandler.new,
EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS:
EditGetAvailableRefactoringsHandler.new,
EDIT_REQUEST_BULK_FIXES: EditBulkFixes.new,
EDIT_REQUEST_GET_FIXES: EditGetFixesHandler.new,
EDIT_REQUEST_GET_REFACTORING: EditGetRefactoringHandler.new,
EDIT_REQUEST_IMPORT_ELEMENTS: EditImportElementsHandler.new,
EDIT_REQUEST_ORGANIZE_DIRECTIVES: EditOrganizeDirectivesHandler.new,
EDIT_REQUEST_SORT_MEMBERS: EditSortMembersHandler.new,
EDIT_REQUEST_GET_STATEMENT_COMPLETION:
EditGetStatementCompletionHandler.new,
EDIT_REQUEST_IS_POSTFIX_COMPLETION_APPLICABLE:
EditIsPostfixCompletionApplicableHandler.new,
EDIT_REQUEST_GET_POSTFIX_COMPLETION: EditGetPostfixCompletionHandler.new,
EDIT_REQUEST_LIST_POSTFIX_COMPLETION_TEMPLATES:
EditListPostfixCompletionTemplatesHandler.new,
//
EXECUTION_REQUEST_CREATE_CONTEXT: ExecutionCreateContextHandler.new,
EXECUTION_REQUEST_DELETE_CONTEXT: ExecutionDeleteContextHandler.new,
EXECUTION_REQUEST_GET_SUGGESTIONS: ExecutionGetSuggestionsHandler.new,
EXECUTION_REQUEST_MAP_URI: ExecutionMapUriHandler.new,
EXECUTION_REQUEST_SET_SUBSCRIPTIONS: ExecutionSetSubscriptionsHandler.new,
//
FLUTTER_REQUEST_GET_WIDGET_DESCRIPTION:
FlutterGetWidgetDescriptionHandler.new,
FLUTTER_REQUEST_SET_WIDGET_PROPERTY_VALUE:
FlutterSetWidgetPropertyValueHandler.new,
FLUTTER_REQUEST_SET_SUBSCRIPTIONS: FlutterSetSubscriptionsHandler.new,
//
SEARCH_REQUEST_FIND_ELEMENT_REFERENCES:
SearchFindElementReferencesHandler.new,
SEARCH_REQUEST_FIND_MEMBER_DECLARATIONS:
SearchFindMemberDeclarationsHandler.new,
SEARCH_REQUEST_FIND_MEMBER_REFERENCES:
SearchFindMemberReferencesHandler.new,
SEARCH_REQUEST_FIND_TOP_LEVEL_DECLARATIONS:
SearchFindTopLevelDeclarationsHandler.new,
SEARCH_REQUEST_GET_ELEMENT_DECLARATIONS:
SearchGetElementDeclarationsHandler.new,
SEARCH_REQUEST_GET_TYPE_HIERARCHY: SearchGetTypeHierarchyHandler.new,
//
SERVER_REQUEST_CANCEL_REQUEST: ServerCancelRequestHandler.new,
SERVER_REQUEST_GET_VERSION: ServerGetVersionHandler.new,
SERVER_REQUEST_SET_CLIENT_CAPABILITIES:
ServerSetClientCapabilitiesHandler.new,
SERVER_REQUEST_SET_SUBSCRIPTIONS: ServerSetSubscriptionsHandler.new,
SERVER_REQUEST_SHUTDOWN: ServerShutdownHandler.new,
//
LSP_REQUEST_HANDLE: LspOverLegacyHandler.new,
};
/// The channel from which requests are received and to which responses should
/// be sent.
final ServerCommunicationChannel channel;
@override
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.
bool statusAnalyzing = false;
/// A set of the [ServerService]s to send notifications for.
Set<ServerService> serverServices = {};
/// A table mapping request ids to cancellation tokens that allow cancelling
/// the request.
///
/// Tokens are removed once a request completes and should not be assumed to
/// exist in this table just because cancellation was requested.
Map<String, CancelableToken> cancellationTokens = {};
/// A set of the [GeneralAnalysisService]s to send notifications for.
Set<GeneralAnalysisService> generalAnalysisServices = {};
/// A table mapping [AnalysisService]s to the file paths for which these
/// notifications should be sent.
Map<AnalysisService, Set<String>> analysisServices = {};
/// The most recently registered set of client capabilities. The default is to
/// have no registered requests and no additional capabilities.
///
/// Must be modified through the [clientCapabilities] setter.
ServerSetClientCapabilitiesParams _clientCapabilities =
ServerSetClientCapabilitiesParams([]);
@override
final editorClientCapabilities = lsp.fixedBasicLspClientCapabilities;
@override
final lsp.LspClientConfiguration lspClientConfiguration;
/// A table mapping [FlutterService]s to the file paths for which these
/// notifications should be sent.
Map<FlutterService, Set<String>> flutterServices = {};
/// The support for Flutter properties.
WidgetDescriptions flutterWidgetDescriptions = WidgetDescriptions();
/// The state used by the completion domain handlers.
final CompletionState completionState = CompletionState();
/// The object used to manage uncompleted refactorings.
late RefactoringManager? _refactoringManager;
/// The context used by the execution domain handlers.
final ExecutionContext executionContext = ExecutionContext();
/// The next search response id.
int nextSearchId = 0;
/// The [Completer] that completes when analysis is complete.
Completer<void>? _onAnalysisCompleteCompleter;
/// The controller that is notified when analysis is started.
final StreamController<bool> _onAnalysisStartedController =
StreamController.broadcast();
/// If the "analysis.analyzedFiles" notification is currently being subscribed
/// to (see [generalAnalysisServices]), and at least one such notification has
/// been sent since the subscription was enabled, the set of analyzed files
/// that was delivered in the most recently sent notification. Otherwise
/// `null`.
Set<String>? prevAnalyzedFiles;
/// The controller for [onAnalysisSetChanged].
final StreamController<void> _onAnalysisSetChangedController =
StreamController.broadcast(sync: true);
/// An optional manager to handle file systems which may not always be
/// available.
final DetachableFileSystemManager? detachableFileSystemManager;
/// The broadcast stream of requests that were discarded because there
/// was another request that made this one irrelevant.
@visibleForTesting
final StreamController<Request> discardedRequests =
StreamController.broadcast(sync: true);
/// The index of the next request from the server to the client.
int nextServerRequestId = 0;
/// A table mapping the ids of requests sent from the server to the client
/// that have not yet received a response, to the completer used to return the
/// response when it has been received.
Map<String, Completer<Response>> pendingServerRequests = {};
/// Whether the server should send LSP notifications.
///
/// This is set once the client sends any LSP request or client capability
/// that depends on LSP functionality.
bool sendLspNotifications = false;
/// Initialize a newly created server to receive requests from and send
/// responses to the given [channel].
///
/// If [rethrowExceptions] is true, then any exceptions thrown by analysis are
/// propagated up the call stack. The default is true to allow analysis
/// exceptions to show up in unit tests, but it should be set to false when
/// running a full analysis server.
LegacyAnalysisServer(
this.channel,
ResourceProvider baseResourceProvider,
AnalysisServerOptions options,
DartSdkManager sdkManager,
AnalyticsManager analyticsManager,
CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder,
InstrumentationService instrumentationService, {
http.Client? httpClient,
ProcessRunner? processRunner,
RequestStatisticsHelper? requestStatistics,
DiagnosticServer? diagnosticServer,
this.detachableFileSystemManager,
// Disable to avoid using this in unit tests.
bool enableBlazeWatcher = false,
DartFixPromptManager? dartFixPromptManager,
super.providedByteStore,
super.pluginManager,
}) : lspClientConfiguration =
lsp.LspClientConfiguration(baseResourceProvider.pathContext),
super(
options,
sdkManager,
diagnosticServer,
analyticsManager,
crashReportingAttachmentsBuilder,
baseResourceProvider,
instrumentationService,
httpClient,
processRunner,
NotificationManager(channel, baseResourceProvider.pathContext),
requestStatistics: requestStatistics,
enableBlazeWatcher: enableBlazeWatcher,
dartFixPromptManager: dartFixPromptManager,
) {
var contextManagerCallbacks =
ServerContextManagerCallbacks(this, resourceProvider);
contextManager.callbacks = contextManagerCallbacks;
analysisDriverSchedulerEventsSubscription =
analysisDriverScheduler.events.listen(handleAnalysisEvent);
analysisDriverScheduler.start();
onAnalysisStarted.first.then((_) {
onAnalysisComplete.then((_) {
performance = performanceAfterStartup = ServerPerformance();
});
});
channel.sendNotification(
ServerConnectedParams(
options.reportProtocolVersion ?? PROTOCOL_VERSION,
io.pid,
).toNotification(clientUriConverter: uriConverter),
);
debounceRequests(channel, discardedRequests)
.listen(handleRequestOrResponse, onDone: done, onError: error);
_newRefactoringManager();
}
/// The most recently registered set of client capabilities. The default is to
/// have no registered requests and no additional capabilities.
ServerSetClientCapabilitiesParams get clientCapabilities =>
_clientCapabilities;
/// Updates the current set of client capabilities.
set clientCapabilities(ServerSetClientCapabilitiesParams capabilities) {
_clientCapabilities = capabilities;
if (capabilities.supportsUris ?? false) {
// URI support implies LSP, as that's the only way to access (and get
// change notifications for) custom-scheme files.
uriConverter = ClientUriConverter.withVirtualFileSupport(
resourceProvider.pathContext);
initializeLspOverLegacy();
} else {
uriConverter = ClientUriConverter.noop(resourceProvider.pathContext);
}
}
/// The [Future] that completes when analysis is complete.
Future<void> get onAnalysisComplete {
if (isAnalysisComplete()) {
return Future.value();
}
var completer = _onAnalysisCompleteCompleter ??= Completer<void>();
return completer.future;
}
/// The stream that is notified when the analysis set is changed - this might
/// be a change to a file, external via a watch event, or internal via
/// overlay. This means that the resolved world might have changed.
///
/// The type of produced elements is not specified and should not be used.
Stream<void> get onAnalysisSetChanged =>
_onAnalysisSetChangedController.stream;
/// The stream that is notified with `true` when analysis is started.
Stream<bool> get onAnalysisStarted {
return _onAnalysisStartedController.stream;
}
@override
OpenUriNotificationSender? get openUriNotificationSender {
if (!clientCapabilities.requests.contains('openUrlRequest')) {
return null;
}
return (Uri uri) async {
var requestId = '${nextServerRequestId++}';
await sendRequest(
ServerOpenUrlRequestParams('$uri')
.toRequest(requestId, clientUriConverter: uriConverter),
);
};
}
RefactoringManager? get refactoringManager {
var refactoringManager = _refactoringManager;
if (refactoringManager == null) {
return null;
}
if (refactoringManager.hasPendingRequest) {
refactoringManager.cancel();
_newRefactoringManager();
}
return _refactoringManager;
}
String get sdkPath {
return sdkManager.defaultSdkDirectory;
}
@override
@protected
bool get supportsShowMessageRequest =>
clientCapabilities.requests.contains('showMessageRequest');
void cancelRequest(String id) {
cancellationTokens[id]?.cancel();
}
/// The socket from which requests are being read has been closed.
void done() {}
/// There was an error related to the socket from which requests are being
/// read.
void error(argument) {}
/// Return the cached analysis result for the file with the given [path].
/// If there is no cached result, return `null`.
ResolvedUnitResult? getCachedResolvedUnit(String path) {
if (!file_paths.isDart(resourceProvider.pathContext, path)) {
return null;
}
var driver = getAnalysisDriver(path);
return driver?.getCachedResolvedUnit(path);
}
/// Gets the current version number of a document.
///
/// For the legacy server we do not track version numbers, these are
/// LSP-specific.
@override
int? getDocumentVersion(String path) => null;
@override
FutureOr<void> handleAnalysisStatusChange(analysis.AnalysisStatus status) {
super.handleAnalysisStatusChange(status);
sendStatusNotificationNew(status);
}
/// Handle a [request] that was read from the communication channel.
void handleRequest(Request request) {
var startTime = DateTime.now();
performance.logRequestTiming(request.clientRequestTime);
// Because we don't `await` the execution of the handlers, we wrap the
// execution in order to have one central place to handle exceptions.
runZonedGuarded(() async {
// Record performance information for the request.
var rootPerformance = OperationPerformanceImpl('<root>');
RequestPerformance? requestPerformance;
await rootPerformance.runAsync('request', (performance) async {
requestPerformance = RequestPerformance(
operation: request.method,
performance: performance,
requestLatency: request.timeSinceRequest,
startTime: startTime,
);
recentPerformance.requests.add(requestPerformance!);
var cancellationToken = CancelableToken();
cancellationTokens[request.id] = cancellationToken;
var generator = requestHandlerGenerators[request.method];
if (generator != null) {
var handler =
generator(this, request, cancellationToken, performance);
if (!handler.recordsOwnAnalytics) {
analyticsManager.startedRequest(
request: request, startTime: startTime);
}
await handler.handle();
} else {
analyticsManager.startedRequest(
request: request, startTime: startTime);
sendResponse(Response.unknownRequest(request));
}
});
if (requestPerformance != null &&
requestPerformance!.performance.elapsed >
ServerRecentPerformance.slowRequestsThreshold) {
recentPerformance.slowRequests.add(requestPerformance!);
}
}, (exception, stackTrace) {
if (exception is InconsistentAnalysisException) {
sendResponse(Response.contentModified(request));
} else if (exception is RequestFailure) {
sendResponse(exception.response);
} else {
// Log the exception.
instrumentationService.logException(
FatalException(
'Failed to handle request: ${request.method}',
exception,
stackTrace,
),
null,
crashReportingAttachmentsBuilder.forException(exception),
);
// Then return an error response to the client.
var error =
RequestError(RequestErrorCode.SERVER_ERROR, exception.toString());
error.stackTrace = stackTrace.toString();
var response = Response(request.id, error: error);
sendResponse(response);
}
});
}
/// Handle a [request] that was read from the communication channel.
void handleRequestOrResponse(RequestOrResponse requestOrResponse) {
if (requestOrResponse is Request) {
messageScheduler.add(LegacyMessage(request: requestOrResponse));
messageScheduler.notify();
} else if (requestOrResponse is Response) {
handleResponse(requestOrResponse);
}
}
/// Handle a [response] that was read from the communication channel.
void handleResponse(Response response) {
var completer = pendingServerRequests.remove(response.id);
if (completer != null) {
completer.complete(response);
}
}
/// Initializes LSP support over the legacy server.
///
/// This method is called when the client sends an LSP request, or indicates
/// that it will use LSP-overy-Legacy via client capabilities.
///
/// This only applies to LSP over the legacy protocol and not DTD, since we
/// do not want a DTD-LSP client to trigger LSP notifications going to the
/// legacy protocol client, only the legacy protocol client should do that.
void initializeLspOverLegacy() {
sendLspNotifications = true;
}
/// Return `true` if the [path] is both absolute and normalized.
bool isAbsoluteAndNormalized(String path) {
var pathContext = resourceProvider.pathContext;
return pathContext.isAbsolute(path) && pathContext.normalize(path) == path;
}
/// Return `true` if analysis is complete.
bool isAnalysisComplete() {
return !analysisDriverScheduler.isWorking;
}
/// Return `true` if the given path is a valid `FilePath`.
///
/// This means that it is absolute and normalized.
bool isValidFilePath(String path) {
return resourceProvider.pathContext.isAbsolute(path) &&
resourceProvider.pathContext.normalize(path) == path;
}
@override
void notifyFlutterWidgetDescriptions(String path) {
flutterWidgetDescriptions.flush();
}
/// Send the given LSP [notification] to the client.
@override
void sendLspNotification(lsp.NotificationMessage notification) {
if (!sendLspNotifications) {
return;
}
channel.sendNotification(
LspNotificationParams(notification)
.toNotification(clientUriConverter: uriConverter),
);
}
/// Send the given [notification] to the client.
void sendNotification(Notification notification) {
channel.sendNotification(notification);
}
/// Send the given [request] to the client.
Future<Response> sendRequest(Request request) {
var completer = Completer<Response>();
pendingServerRequests[request.id] = completer;
channel.sendRequest(request);
return completer.future;
}
/// Send the given [response] to the client.
void sendResponse(Response response) {
channel.sendResponse(response);
analyticsManager.sentResponse(response: response);
cancellationTokens.remove(response.id);
}
/// If the [path] is not a valid file path, that is absolute and normalized,
/// send an error response, and return `true`. If OK then return `false`.
bool sendResponseErrorIfInvalidFilePath(Request request, String path) {
if (!isAbsoluteAndNormalized(path)) {
sendResponse(Response.invalidFilePathFormat(request, path));
return true;
}
return false;
}
/// Sends a `server.error` notification.
@override
void sendServerErrorNotification(
String message,
dynamic exception,
/*StackTrace*/ stackTrace, {
bool fatal = false,
}) {
var msg = exception == null ? message : '$message: $exception';
if (stackTrace != null && exception is! CaughtException) {
stackTrace = StackTrace.current;
}
// send the notification
channel.sendNotification(ServerErrorParams(fatal, msg, '$stackTrace')
.toNotification(clientUriConverter: uriConverter));
// remember the last few exceptions
if (exception is CaughtException) {
stackTrace ??= exception.stackTrace;
}
exceptions.add(ServerException(
message,
exception,
stackTrace is StackTrace ? stackTrace : StackTrace.current,
fatal,
));
}
/// 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) {
_onAnalysisStartedController.add(true);
}
var onAnalysisCompleteCompleter = _onAnalysisCompleteCompleter;
if (onAnalysisCompleteCompleter != null && !isAnalyzing) {
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));
}
/// Implementation for `analysis.setAnalysisRoots`.
///
// TODO(scheglov): implement complete projects/contexts semantics.
//
// The current implementation is intentionally simplified and expected
// that only folders are given each given folder corresponds to the exactly
// one context.
//
// So, we can start working in parallel on adding services and improving
// projects/contexts support.
Future<void> setAnalysisRoots(String requestId, List<String> includedPaths,
List<String> excludedPaths) async {
var completer = analysisContextRebuildCompleter = Completer();
try {
notificationManager.setAnalysisRoots(includedPaths, excludedPaths);
try {
await contextManager.setRoots(includedPaths, excludedPaths);
} on UnimplementedError catch (e) {
throw RequestFailure(Response.unsupportedFeature(
requestId, e.message ?? 'Unsupported feature.'));
}
} finally {
completer.complete();
}
}
/// Implementation for `analysis.setSubscriptions`.
void setAnalysisSubscriptions(
Map<AnalysisService, Set<String>> subscriptions) {
notificationManager.setSubscriptions(subscriptions);
analysisServices = subscriptions;
_sendSubscriptions(analysis: true);
}
/// Implementation for `flutter.setSubscriptions`.
void setFlutterSubscriptions(Map<FlutterService, Set<String>> subscriptions) {
flutterServices = subscriptions;
_sendSubscriptions(flutter: true);
}
/// Implementation for `analysis.setGeneralSubscriptions`.
void setGeneralAnalysisSubscriptions(
List<GeneralAnalysisService> subscriptions) {
var newServices = subscriptions.toSet();
if (newServices.contains(GeneralAnalysisService.ANALYZED_FILES) &&
!generalAnalysisServices
.contains(GeneralAnalysisService.ANALYZED_FILES) &&
isAnalysisComplete()) {
sendAnalysisNotificationAnalyzedFiles(this);
} else if (!newServices.contains(GeneralAnalysisService.ANALYZED_FILES) &&
generalAnalysisServices
.contains(GeneralAnalysisService.ANALYZED_FILES)) {
prevAnalyzedFiles = null;
}
generalAnalysisServices = newServices;
}
/// Set the priority files to the given [files].
void setPriorityFiles(String requestId, List<String> files) {
bool isPubspec(String filePath) =>
file_paths.isPubspecYaml(resourceProvider.pathContext, filePath);
// When pubspecs are opened, trigger pre-loading of pub package names and
// versions.
var pubspecs = files.where(isPubspec).toList();
if (pubspecs.isNotEmpty) {
pubPackageService.beginCachePreloads(pubspecs);
}
priorityFiles.clear();
priorityFiles.addAll(files);
// Set priority files in drivers.
for (var driver in driverMap.values) {
driver.priorityFiles = files;
}
}
@override
@visibleForOverriding
Future<String?> showUserPrompt(
MessageType type,
String message,
List<String> actionLabels,
) async {
assert(supportsShowMessageRequest);
var requestId = (nextServerRequestId++).toString();
var actions = actionLabels.map((label) => MessageAction(label)).toList();
var request =
ServerShowMessageRequestParams(type.forLegacy, message, actions)
.toRequest(requestId, clientUriConverter: uriConverter);
var response = await sendRequest(request);
return response.result?['action'] as String?;
}
@override
Future<void> shutdown() async {
await super.shutdown();
pubApi.close();
detachableFileSystemManager?.dispose();
// Defer closing the channel and shutting down the instrumentation server so
// that the shutdown response can be sent and logged.
unawaited(Future(() {
instrumentationService.shutdown();
channel.close();
}));
}
/// Implementation for `analysis.updateContent`.
void updateContent(String id, Map<String, dynamic> changes) {
_onAnalysisSetChangedController.add(null);
changes.forEach((file, change) {
// Prepare the old overlay contents.
String? oldContents;
try {
if (resourceProvider.hasOverlay(file)) {
oldContents = resourceProvider.getFile(file).readAsStringSync();
}
} catch (_) {}
// Prepare the new contents.
String? newContents;
if (change is AddContentOverlay) {
newContents = change.content;
} else if (change is ChangeContentOverlay) {
if (oldContents == null) {
// The client may only send a ChangeContentOverlay if there is
// already an existing overlay for the source.
throw RequestFailure(Response(id,
error: RequestError(RequestErrorCode.INVALID_OVERLAY_CHANGE,
'Invalid overlay change')));
}
try {
newContents = SourceEdit.applySequence(oldContents, change.edits);
} on RangeError {
throw RequestFailure(Response(id,
error: RequestError(RequestErrorCode.INVALID_OVERLAY_CHANGE,
'Invalid overlay change')));
}
} else if (change is RemoveContentOverlay) {
newContents = null;
} else {
// Protocol parsing should have ensured that we never get here.
throw AnalysisException('Illegal change type');
}
if (newContents != null) {
resourceProvider.setOverlay(
file,
content: newContents,
modificationStamp: overlayModificationStamp++,
);
} else {
resourceProvider.removeOverlay(file);
}
for (var driver in driverMap.values) {
driver.changeFile(file);
}
// If the file did not exist, and is "overlay only", it still should be
// analyzed. Add it to driver to which it should have been added.
contextManager.getDriverFor(file)?.addFile(file);
notifyDeclarationsTracker(file);
notifyFlutterWidgetDescriptions(file);
// TODO(scheglov): implement other cases
});
}
/// Use the given updaters to update the values of the options in every
/// existing analysis context.
void updateOptions(List<OptionUpdater> optionUpdaters) {
// TODO(scheglov): implement for the new analysis driver
// //
// // Update existing contexts.
// //
// for (AnalysisContext context in analysisContexts) {
// AnalysisOptionsImpl options =
// new AnalysisOptionsImpl.from(context.analysisOptions);
// optionUpdaters.forEach((OptionUpdater optionUpdater) {
// optionUpdater(options);
// });
// context.analysisOptions = options;
// // `TODO`(brianwilkerson) As far as I can tell, this doesn't cause analysis
// // to be scheduled for this context.
// }
// //
// // Update the defaults used to create new contexts.
// //
// optionUpdaters.forEach((OptionUpdater optionUpdater) {
// optionUpdater(defaultContextOptions);
// });
}
/// Returns `true` if there is a subscription for the given [service] and
/// [file].
bool _hasAnalysisServiceSubscription(AnalysisService service, String file) {
return analysisServices[service]?.contains(file) ?? false;
}
bool _hasFlutterServiceSubscription(FlutterService service, String file) {
return flutterServices[service]?.contains(file) ?? false;
}
/// Initializes [_refactoringManager] with a new instance.
void _newRefactoringManager() {
_refactoringManager = RefactoringManager(this, refactoringWorkspace);
}
void _scheduleAnalysisImplementedNotification() {
var subscribed = analysisServices[AnalysisService.IMPLEMENTED];
if (subscribed == null) {
return;
}
var toSend = subscribed.intersection(filesResolvedSinceLastIdle);
if (toSend.isEmpty) {
return;
}
scheduleImplementedNotification(this, toSend);
}
void _sendSubscriptions({bool analysis = false, bool flutter = false}) {
var files = <String>{};
if (analysis) {
for (var serviceFiles in analysisServices.values) {
files.addAll(serviceFiles);
}
}
if (flutter) {
for (var serviceFiles in flutterServices.values) {
files.addAll(serviceFiles);
}
}
for (var file in files) {
// The result will be produced by the "results" stream with
// the fully resolved unit, and processed with sending analysis
// notifications as it happens after content changes.
if (file_paths.isDart(resourceProvider.pathContext, file)) {
getResolvedUnit(file, sendCachedToStream: true);
}
}
}
}
class ServerContextManagerCallbacks
extends CommonServerContextManagerCallbacks {
@override
final LegacyAnalysisServer analysisServer;
ServerContextManagerCallbacks(this.analysisServer, super.resourceProvider);
AbstractNotificationManager get _notificationManager =>
analysisServer.notificationManager;
@override
void afterContextsCreated() {
super.afterContextsCreated();
analysisServer._sendSubscriptions(analysis: true, flutter: true);
}
@override
void afterWatchEvent(WatchEvent event) {
analysisServer._onAnalysisSetChangedController.add(null);
}
@override
void flushResults(List<String> files) {
sendAnalysisNotificationFlushResults(analysisServer, files);
}
@override
void handleResolvedUnitResult(ResolvedUnitResult result) {
var path = result.path;
var unit = result.unit;
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.HIGHLIGHTS, path)) {
_runDelayed(() {
_notificationManager.recordHighlightRegions(
NotificationManager.serverId, path, _computeHighlightRegions(unit));
});
}
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.NAVIGATION, path)) {
_runDelayed(() {
_notificationManager.recordNavigationParams(
NotificationManager.serverId,
path,
_computeNavigationParams(path, result));
});
}
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.OCCURRENCES, path)) {
_runDelayed(() {
_notificationManager.recordOccurrences(
NotificationManager.serverId, path, _computeOccurrences(unit));
});
}
// if (analysisServer._hasAnalysisServiceSubscription(
// AnalysisService.OUTLINE, path)) {
// _runDelayed(() {
// // `TODO`(brianwilkerson) Change NotificationManager to store params
// // so that fileKind and libraryName can be recorded / passed along.
// notificationManager.recordOutlines(NotificationManager.serverId, path,
// _computeOutlineParams(path, unit, result.lineInfo));
// });
// }
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.CLOSING_LABELS, path)) {
_runDelayed(() {
sendAnalysisNotificationClosingLabels(
analysisServer, path, result.lineInfo, unit);
});
}
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.FOLDING, path)) {
_runDelayed(() {
sendAnalysisNotificationFolding(
analysisServer, path, result.lineInfo, unit);
});
}
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.OUTLINE, path)) {
_runDelayed(() {
sendAnalysisNotificationOutline(analysisServer, result);
});
}
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.OVERRIDES, path)) {
_runDelayed(() {
sendAnalysisNotificationOverrides(analysisServer, path, unit);
});
}
if (analysisServer._hasFlutterServiceSubscription(
FlutterService.OUTLINE, path)) {
_runDelayed(() {
sendFlutterNotificationOutline(analysisServer, result);
});
}
}
List<HighlightRegion> _computeHighlightRegions(CompilationUnit unit) {
return DartUnitHighlightsComputer(unit).compute();
}
server.AnalysisNavigationParams _computeNavigationParams(
String path, ParsedUnitResult result) {
var collector = NavigationCollectorImpl();
computeDartNavigation(resourceProvider, collector, result, null, null);
collector.createRegions();
return server.AnalysisNavigationParams(
path, collector.regions, collector.targets, collector.files);
}
List<Occurrences> _computeOccurrences(CompilationUnit unit) {
var collector = OccurrencesCollectorImpl();
addDartOccurrences(collector, unit);
return collector.allOccurrences;
}
/// Run [f] in a new [Future].
///
/// This method is used to delay sending notifications. If there is a more
/// important consumer of an analysis results, specifically a code completion
/// computer, we want it to run before spending time of sending notifications.
///
// TODO(scheglov): Consider replacing this with full priority based scheduler.
//
// TODO(scheglov): Alternatively, if code completion work in a way that does
// not produce (at first) fully resolved unit, but only part of it - a single
// method, or a top-level declaration, we would not have this problem - the
// completion computer would be the only consumer of the partial analysis
// result.
void _runDelayed(Function() f) {
Future(f);
}
}
/// Used to record server exceptions.
class ServerException {
final String message;
final dynamic exception;
final StackTrace stackTrace;
final bool fatal;
ServerException(this.message, this.exception, this.stackTrace, this.fatal);
@override
String toString() => message;
}
/// A class used by [LegacyAnalysisServer] to record performance information
/// such as request latency.
class ServerPerformance {
/// The creation time and the time when performance information
/// started to be recorded here.
final int startTime = DateTime.now().millisecondsSinceEpoch;
/// The number of requests.
int requestCount = 0;
/// The number of requests that recorded latency information.
int latencyCount = 0;
/// The total latency (milliseconds) for all recorded requests.
int requestLatency = 0;
/// The maximum latency (milliseconds) for all recorded requests.
int maxLatency = 0;
/// The number of requests with latency > 150 milliseconds.
int slowRequestCount = 0;
/// Log timing information for a request.
void logRequestTiming(int? clientRequestTime) {
++requestCount;
if (clientRequestTime != null) {
var latency = DateTime.now().millisecondsSinceEpoch - clientRequestTime;
++latencyCount;
requestLatency += latency;
maxLatency = max(maxLatency, latency);
if (latency > 150) {
++slowRequestCount;
}
}
}
}