// Copyright (c) 2024, 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 'package:analysis_server_plugin/edit/assist/assist.dart'; import 'package:analysis_server_plugin/edit/assist/dart_assist_context.dart'; import 'package:analysis_server_plugin/edit/fix/dart_fix_context.dart'; import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analysis_server_plugin/plugin.dart'; import 'package:analysis_server_plugin/src/correction/assist_processor.dart'; import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart'; import 'package:analysis_server_plugin/src/correction/fix_processor.dart'; import 'package:analysis_server_plugin/src/registry.dart'; import 'package:analyzer/analysis_rule/rule_context.dart'; import 'package:analyzer/dart/analysis/analysis_context.dart'; import 'package:analyzer/dart/analysis/analysis_context_collection.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/diagnostic/diagnostic.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/overlay_file_system.dart'; import 'package:analyzer/instrumentation/instrumentation.dart'; import 'package:analyzer/src/analysis_rule/rule_context.dart'; import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/src/dart/analysis/analysis_options.dart'; import 'package:analyzer/src/dart/analysis/byte_store.dart'; import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/type_system.dart'; import 'package:analyzer/src/ignore_comments/ignore_info.dart'; import 'package:analyzer/src/lint/config.dart'; import 'package:analyzer/src/lint/linter_visitor.dart'; import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer/src/util/file_paths.dart' as file_paths; import 'package:analyzer_plugin/channel/channel.dart'; import 'package:analyzer_plugin/protocol/protocol.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/src/protocol/protocol_internal.dart'; import 'package:meta/meta.dart'; /// A pair, matching a [Diagnostic] with it's equivalent /// [protocol.AnalysisError]. typedef _DiagnosticAndAnalysisError = ({ Diagnostic diagnostic, protocol.AnalysisError analysisError, }); typedef _PluginState = ({ AnalysisContext analysisContext, List<_DiagnosticAndAnalysisError> errors, }); /// The server that communicates with the analysis server, passing requests and /// responses between the analysis server and individual plugins. class PluginServer { /// The communication channel being used to communicate with the analysis /// server. late PluginCommunicationChannel _channel; final OverlayResourceProvider _resourceProvider; late final ByteStore _byteStore = MemoryCachingByteStore( NullByteStore(), 1024 * 1024 * 256, ); AnalysisContextCollectionImpl? _contextCollection; String? _sdkPath; /// Paths of priority files. Set _priorityPaths = {}; final List _plugins; /// The recent state of analysis reults, to be cleared on file changes. final _recentState = {}; /// The next modification stamp for a changed file in the [_resourceProvider]. int _overlayModificationStamp = 0; /// The list of registered features for each plugin, for reporting purposes. final _registries = []; PluginServer({ required ResourceProvider resourceProvider, required List plugins, }) : _resourceProvider = OverlayResourceProvider(resourceProvider), _plugins = plugins { for (var plugin in plugins) { var registry = PluginRegistryImpl(plugin.name); _registries.add(registry); plugin.register(registry); } PluginRegistryImpl.registerIgnoreProducerGenerators(); } @visibleForTesting Set get priorityPaths => {..._priorityPaths}; /// Handles an 'analysis.setPriorityFiles' request. /// /// Throws a [RequestFailure] if the request could not be handled. Future handleAnalysisSetPriorityFiles( protocol.AnalysisSetPriorityFilesParams parameters, ) async { _priorityPaths = parameters.files.toSet(); return protocol.AnalysisSetPriorityFilesResult(); } /// Handles an 'edit.getAssists' request. /// /// Throws a [RequestFailure] if the request could not be handled. Future handleEditGetAssists( protocol.EditGetAssistsParams parameters, ) async { var path = parameters.file; var recentState = _recentState[path]; if (recentState == null) { return protocol.EditGetAssistsResult(const []); } var (:analysisContext, :errors) = recentState; var libraryResult = await analysisContext.currentSession.getResolvedLibrary( path, ); if (libraryResult is! ResolvedLibraryResult) { return protocol.EditGetAssistsResult(const []); } var unitResult = libraryResult.unitWithPath(path); if (unitResult is! ResolvedUnitResult) { return protocol.EditGetAssistsResult(const []); } var context = DartAssistContext( // TODO(srawlins): Use a real instrumentation service. Other // implementations get InstrumentationService from AnalysisServer. InstrumentationService.NULL_SERVICE, DartChangeWorkspace([analysisContext.currentSession]), libraryResult, unitResult, parameters.offset, parameters.length, ); List assists; try { assists = await computeAssists(context); } on InconsistentAnalysisException { // TODO(srawlins): Is it important to at least log this? Or does it // happen on the regular? assists = []; } if (assists.isEmpty) { return protocol.EditGetAssistsResult(const []); } var corrections = [ for (var assist in assists..sort(Assist.compareAssists)) protocol.PrioritizedSourceChange(assist.kind.priority, assist.change), ]; return protocol.EditGetAssistsResult(corrections); } /// Handles an 'edit.getFixes' request. /// /// Throws a [RequestFailure] if the request could not be handled. Future handleEditGetFixes( protocol.EditGetFixesParams parameters, ) async { var path = parameters.file; var offset = parameters.offset; var recentState = _recentState[path]; if (recentState == null) { return protocol.EditGetFixesResult(const []); } var (:analysisContext, :errors) = recentState; var libraryResult = await analysisContext.currentSession.getResolvedLibrary( path, ); if (libraryResult is! ResolvedLibraryResult) { return protocol.EditGetFixesResult(const []); } var unitResult = libraryResult.unitWithPath(path); if (unitResult is! ResolvedUnitResult) { return protocol.EditGetFixesResult(const []); } var lineInfo = unitResult.lineInfo; var requestLine = lineInfo.getLocation(offset).lineNumber; var lintAtOffset = errors.where((error) { var errorLine = lineInfo.getLocation(error.diagnostic.offset).lineNumber; return errorLine == requestLine; }); if (lintAtOffset.isEmpty) return protocol.EditGetFixesResult(const []); var errorFixesList = []; var workspace = DartChangeWorkspace([analysisContext.currentSession]); for (var (:diagnostic, :analysisError) in lintAtOffset) { var context = DartFixContext( // TODO(srawlins): Use a real instrumentation service. Other // implementations get InstrumentationService from AnalysisServer. instrumentationService: InstrumentationService.NULL_SERVICE, workspace: workspace, libraryResult: libraryResult, unitResult: unitResult, error: diagnostic, ); List fixes; try { fixes = await computeFixes(context); } on InconsistentAnalysisException { // TODO(srawlins): Is it important to at least log this? Or does it // happen on the regular? fixes = []; } if (fixes.isNotEmpty) { fixes.sort(Fix.compareFixes); var errorFixes = protocol.AnalysisErrorFixes(analysisError); errorFixesList.add(errorFixes); for (var fix in fixes) { errorFixes.fixes.add(protocol.PrioritizedSourceChange(1, fix.change)); } } } return protocol.EditGetFixesResult(errorFixesList); } /// Handles a 'plugin.versionCheck' request. Future handlePluginVersionCheck( protocol.PluginVersionCheckParams parameters, ) async { // TODO(srawlins): It seems improper for _this_ method to be the point where // the SDK path is configured... _sdkPath = parameters.sdkPath; return protocol.PluginVersionCheckResult(true, 'Plugin Server', '0.0.1', [ '**.dart', ]); } /// Initializes each of the registered plugins. Future initialize() async { await Future.wait( _plugins.map((p) => p.start()).whereType>(), ); } /// Starts this plugin by listening to the given communication [channel]. void start(PluginCommunicationChannel channel) { _channel = channel; _channel.listen( _handleRequestZoned, // TODO(srawlins): Implement. onDone: () {}, ); } /// This method is invoked when a new instance of [AnalysisContextCollection] /// is created, so the plugin can perform initial analysis of analyzed files. 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() // TODO(srawlins): Enable analysis on other files, even if only // YAML files for analysis options and pubspec analysis and quick // fixes. .where((p) => file_paths.isDart(_resourceProvider.pathContext, p)) .toSet(); await _analyzeLibraries(analysisContext: analysisContext, paths: paths); }); _channel.sendNotification( protocol.PluginStatusParams( analysis: protocol.AnalysisStatus(false), ).toNotification(), ); } /// Analyzes the library at the given [libraryPath], sending an /// 'analysis.errors' [Notification] for each compilation unit. Future _analyzeLibrary({ required AnalysisContext analysisContext, required String libraryPath, }) async { var file = _resourceProvider.getFile(libraryPath); var analysisOptions = analysisContext.getAnalysisOptionsForFile(file); var analysisErrorsByPath = await _computeAnalysisErrors( analysisContext, libraryPath, analysisOptions: analysisOptions as AnalysisOptionsImpl, ); for (var MapEntry(key: path, value: analysisErrors) in analysisErrorsByPath.entries) { _channel.sendNotification( protocol.AnalysisErrorsParams(path, analysisErrors).toNotification(), ); } } /// Analyzes the libraries at the given [paths]. // TODO(srawlins): Refactor how libraries are analyzed using AnalysisDriver, // to be similar to what analysis server does: // // 1. When the analysis roots change it creates the AnalysisContextCollection // and listens to the drivers' streams of results. // 2. When a file changes, it lets the driver know about it. // 3. As the driver analyzes the potentially impacted files it // a. runs the lints that have been enabled and // b. puts the result on the stream. // 4. When a result is on the stream the server grabs the results and sends // the diagnostics to the client. // // It doesn't ever explicitly ask the driver which files were impacted and it // doesn't explicitly run the lints directly because the driver will do that // implicitly. There would be benefits to plugins working the same way: // // * The driver can do a more efficient job of scheduling analysis than the // server can (because of having a more complete picture). // * It means there's only one way that we're trying to use the analyzer so it // will be easier to make changes to the analyzer when we need to (smaller // API exposure). // * The logic for doing analysis is in one place so it's easier to reason // about. // * We don't need to be familiar with two different architectures. Future _analyzeLibraries({ required AnalysisContext analysisContext, required Set paths, }) async { // First analyze priority files. for (var path in _priorityPaths) { if (paths.remove(path)) { await _analyzeLibrary( analysisContext: analysisContext, libraryPath: path, ); } } // Then analyze the remaining files. for (var path in paths) { await _analyzeLibrary( analysisContext: analysisContext, libraryPath: path, ); } } /// Computes and returns [protocol.AnalysisError]s for each of the parts in /// the library at [libraryPath]. Future>> _computeAnalysisErrors( AnalysisContext analysisContext, String libraryPath, { required AnalysisOptionsImpl analysisOptions, }) async { var libraryResult = await analysisContext.currentSession.getResolvedLibrary( libraryPath, ); if (libraryResult is! ResolvedLibraryResult) { // We only handle analyzing at the library-level. Below, we work through // each of the compilation units found in `libraryResult`. return const {}; } var diagnosticListeners = { for (var unitResult in libraryResult.units) unitResult: RecordingDiagnosticListener(), }; RuleContextUnit? definingContextUnit; var definingUnit = (libraryResult.element as LibraryElementImpl).firstFragment; var allUnits = []; for (var unitResult in libraryResult.units) { var contextUnit = RuleContextUnit( file: unitResult.file, content: unitResult.content, diagnosticReporter: DiagnosticReporter( diagnosticListeners[unitResult]!, unitResult.libraryElement.firstFragment.source, ), unit: unitResult.unit, ); allUnits.add(contextUnit); if (unitResult.unit.declaredFragment == definingUnit) { definingContextUnit = contextUnit; } } // Just a fallback value. We shouldn't get into this situation, but this is // a safe default. definingContextUnit ??= allUnits.first; // TODO(srawlins): Enable timing similar to what the linter package's // `benchmark.dart` script does. var nodeRegistry = RuleVisitorRegistryImpl(enableTiming: false); var package = analysisContext.contextRoot.workspace.findPackageFor( libraryPath, ); var context = RuleContextWithResolvedResults( allUnits, definingContextUnit, libraryResult.element.typeProvider, libraryResult.element.typeSystem as TypeSystemImpl, package, ); // A mapping from each diagnostic code to its corresponding plugin. var pluginCodeMapping = {}; // A mapping from each diagnostic code to its configured severity. var severityMapping = {}; for (var configuration in analysisOptions.pluginConfigurations) { if (!configuration.isEnabled) continue; // TODO(srawlins): Namespace rules by their plugin, to avoid collisions. var rules = Registry.ruleRegistry.enabled( configuration.diagnosticConfigs, ); for (var code in rules.expand((r) => r.diagnosticCodes)) { pluginCodeMapping.putIfAbsent(code, () => configuration.name); severityMapping.putIfAbsent( code, () => _configuredSeverity(configuration, code), ); } for (var rule in rules) { // TODO(srawlins): Enable timing similar to what the linter package's // `benchmark.dart` script does. rule.registerNodeProcessors(nodeRegistry, context); } // Now to perform the actual analysis. for (var currentUnit in allUnits) { for (var rule in rules) { rule.reporter = currentUnit.diagnosticReporter; } context.currentUnit = currentUnit; currentUnit.unit.accept( AnalysisRuleVisitor(nodeRegistry, shouldPropagateExceptions: true), ); } } // TODO(srawlins): Support `AnalysisRuleVisitor.afterLibrary`. See how it is // used in `library_analyzer.dart`. // The list of the `AnalysisError`s and their associated // `protocol.AnalysisError`s. var diagnosticsAndAnalysisErrors = <({Diagnostic diagnostic, protocol.AnalysisError analysisError})>[]; diagnosticListeners.forEach((unitResult, listener) { var ignoreInfo = IgnoreInfo.forDart(unitResult.unit, unitResult.content); var diagnostics = listener.diagnostics.where((e) { var pluginName = pluginCodeMapping[e.diagnosticCode]; if (pluginName == null) { // If [e] is somehow not mapped, something is wrong; but don't mark it // as ignored. return true; } return !ignoreInfo.ignored(e, pluginName: pluginName); }); for (var diagnostic in diagnostics) { diagnosticsAndAnalysisErrors.add(( diagnostic: diagnostic, analysisError: protocol.AnalysisError( severityMapping[diagnostic.diagnosticCode] ?? protocol.AnalysisErrorSeverity.INFO, protocol.AnalysisErrorType.STATIC_WARNING, _locationFor(unitResult.unit, unitResult.path, diagnostic), diagnostic.message, diagnostic.diagnosticCode.name, correction: diagnostic.correctionMessage, // TODO(srawlins): Use a valid value here. hasFix: true, ), )); } }); _recentState[libraryPath] = ( analysisContext: analysisContext, errors: [...diagnosticsAndAnalysisErrors], ); // A map that has a key for each unit's path. It is important to collect the // analysis errors for each unit, even if it has none. We must send a // notification for each unit, even if there are no analysis errors to // report. var analysisErrorsByPath = >{ for (var unitResult in libraryResult.units) unitResult.path: [], }; for (var (diagnostic: _, :analysisError) in diagnosticsAndAnalysisErrors) { analysisErrorsByPath[analysisError.location.file]!.add(analysisError); } return analysisErrorsByPath; } /// Converts the severity of [code] into a [protocol.AnalysisErrorSeverity]. protocol.AnalysisErrorSeverity? _configuredSeverity( PluginConfiguration configuration, DiagnosticCode code, ) { var configuredSeverity = configuration.diagnosticConfigs[code.name]?.severity; if (configuredSeverity != null && configuredSeverity != ConfiguredSeverity.enable) { var severityName = configuredSeverity.name.toUpperCase(); var severity = protocol.AnalysisErrorSeverity.values .asNameMap()[severityName]; assert( severity != null, 'Invalid configured severity: ${configuredSeverity.name}', ); return severity; } // Fall back to the declared severity of [code]. var severityName = code.severity.name.toUpperCase(); var severity = protocol.AnalysisErrorSeverity.values .asNameMap()[code.severity.name]; assert(severity != null, 'Invalid severity: $severityName'); return severity; } /// Invokes [fn] first for priority analysis contexts, then for the rest. Future _forAnalysisContexts( AnalysisContextCollection contextCollection, Future Function(AnalysisContext analysisContext) fn, ) async { var nonPriorityAnalysisContexts = []; for (var analysisContext in contextCollection.contexts) { if (_isPriorityAnalysisContext(analysisContext)) { await fn(analysisContext); } else { nonPriorityAnalysisContexts.add(analysisContext); } } for (var analysisContext in nonPriorityAnalysisContexts) { await fn(analysisContext); } } /// Computes the response for the given [request]. Future _getResponse(Request request, int requestTime) async { ResponseResult? result; switch (request.method) { case protocol.ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS: var params = protocol.AnalysisHandleWatchEventsParams.fromRequest( request, ); result = await _handleAnalysisWatchEvents(params); case protocol.ANALYSIS_REQUEST_SET_CONTEXT_ROOTS: var params = protocol.AnalysisSetContextRootsParams.fromRequest( request, ); result = await _handleAnalysisSetContextRoots(params); case protocol.ANALYSIS_REQUEST_SET_PRIORITY_FILES: var params = protocol.AnalysisSetPriorityFilesParams.fromRequest( request, ); result = await handleAnalysisSetPriorityFiles(params); case protocol.ANALYSIS_REQUEST_UPDATE_CONTENT: var params = protocol.AnalysisUpdateContentParams.fromRequest(request); result = await _handleAnalysisUpdateContent(params); case protocol.EDIT_REQUEST_GET_ASSISTS: var params = protocol.EditGetAssistsParams.fromRequest(request); result = await handleEditGetAssists(params); case protocol.EDIT_REQUEST_GET_FIXES: var params = protocol.EditGetFixesParams.fromRequest(request); result = await handleEditGetFixes(params); case protocol.PLUGIN_REQUEST_DETAILS: var details = []; for (var pluginRegistry in _registries) { var assists = [ for (var assistKind in pluginRegistry.assistKinds) protocol.AssistDescription(assistKind.id, assistKind.message), ]; var fixes = [ for (var MapEntry(key: fixKind, value: codes) in pluginRegistry.fixKinds.entries) protocol.FixDescription(fixKind.id, fixKind.message, codes), ]; details.add( protocol.PluginDetails( pluginRegistry.pluginName, pluginRegistry.lintRules, pluginRegistry.warningRules, assists, fixes, ), ); } result = protocol.PluginDetailsResult(details); case protocol.PLUGIN_REQUEST_SHUTDOWN: _channel.sendResponse( protocol.PluginShutdownResult().toResponse(request.id, requestTime), ); _channel.close(); return null; case protocol.PLUGIN_REQUEST_VERSION_CHECK: var params = protocol.PluginVersionCheckParams.fromRequest(request); result = await handlePluginVersionCheck(params); default: // Anything else is unsupported. result = null; } if (result == null) { return Response( request.id, requestTime, error: RequestErrorFactory.unknownRequest(request.method), ); } return result.toResponse(request.id, requestTime); } /// Handles files that might have been affected by a content change of /// one or more files. The implementation may check if these files should /// be analyzed, do such analysis, and send diagnostics. /// /// By default invokes [_analyzeLibraries] only for files that are analyzed in /// this [analysisContext]. Future _handleAffectedFiles({ required AnalysisContext analysisContext, required List paths, }) async { var analyzedPaths = paths .where(analysisContext.contextRoot.isAnalyzed) .toSet(); await _analyzeLibraries( analysisContext: analysisContext, paths: analyzedPaths, ); } /// Handles an 'analysis.setContextRoots' request. Future _handleAnalysisSetContextRoots( protocol.AnalysisSetContextRootsParams parameters, ) async { var currentContextCollection = _contextCollection; if (currentContextCollection != null) { _contextCollection = null; await currentContextCollection.dispose(); } var includedPaths = parameters.roots.map((e) => e.root).toList(); var contextCollection = AnalysisContextCollectionImpl( resourceProvider: _resourceProvider, includedPaths: includedPaths, byteStore: _byteStore, sdkPath: _sdkPath, fileContentCache: FileContentCache(_resourceProvider), updateAnalysisOptions4: // Disable extra warning computation and lint computation, because // these are reported in the main analysis server isolate, not in the // plugins isolate. ({required AnalysisOptionsImpl analysisOptions}) => analysisOptions ..warning = false ..lint = false, ); _contextCollection = contextCollection; await _analyzeAllFilesInContextCollection( contextCollection: contextCollection, ); return protocol.AnalysisSetContextRootsResult(); } /// Handles an 'analysis.updateContent' request. /// /// Throws a [RequestFailure] if the request could not be handled. Future _handleAnalysisUpdateContent( protocol.AnalysisUpdateContentParams parameters, ) async { var changedPaths = {}; var paths = parameters.files; paths.forEach((String path, Object overlay) { // Prepare the old overlay contents. String? oldContent; try { if (_resourceProvider.hasOverlay(path)) { oldContent = _resourceProvider.getFile(path).readAsStringSync(); } } catch (_) { // Leave `oldContent` empty. } // Prepare the new contents. String? newContent; if (overlay is protocol.AddContentOverlay) { newContent = overlay.content; } else if (overlay is protocol.ChangeContentOverlay) { if (oldContent == null) { // The server should only send a ChangeContentOverlay if there is // already an existing overlay for the source. throw RequestFailure( RequestErrorFactory.invalidOverlayChangeNoContent(), ); } try { newContent = protocol.SourceEdit.applySequence( oldContent, overlay.edits, ); } on RangeError { throw RequestFailure( RequestErrorFactory.invalidOverlayChangeInvalidEdit(), ); } } else if (overlay is protocol.RemoveContentOverlay) { newContent = null; } if (newContent != null) { _resourceProvider.setOverlay( path, content: newContent, modificationStamp: _overlayModificationStamp++, ); } else { _resourceProvider.removeOverlay(path); } changedPaths.add(path); }); await _handleContentChanged(modifiedPaths: changedPaths.toList()); return protocol.AnalysisUpdateContentResult(); } /// Handles an 'analysis.handleWatchEvents' request. Future _handleAnalysisWatchEvents( protocol.AnalysisHandleWatchEventsParams parameters, ) async { final addedPaths = parameters.events .where((e) => e.type == protocol.WatchEventType.ADD) .map((e) => e.path) .toList(); final modifiedPaths = parameters.events .where((e) => e.type == protocol.WatchEventType.MODIFY) .map((e) => e.path) .toList(); final removedPaths = parameters.events .where((e) => e.type == protocol.WatchEventType.REMOVE) .map((e) => e.path) .toList(); await _handleContentChanged( addedPaths: addedPaths, modifiedPaths: modifiedPaths, removedPaths: removedPaths, ); return protocol.AnalysisHandleWatchEventsResult(); } /// Handles added files, modified files, and removed files. Future _handleContentChanged({ List addedPaths = const [], List modifiedPaths = const [], List removedPaths = const [], }) async { if (_contextCollection case var contextCollection?) { _channel.sendNotification( protocol.PluginStatusParams( analysis: protocol.AnalysisStatus(true), ).toNotification(), ); await _forAnalysisContexts(contextCollection, (analysisContext) async { for (var path in modifiedPaths) { analysisContext.changeFile(path); } for (var path in removedPaths) { analysisContext.changeFile(path); } var affected = [ ...await analysisContext.applyPendingFileChanges(), ...addedPaths, ]; await _handleAffectedFiles( analysisContext: analysisContext, paths: affected, ); }); _channel.sendNotification( protocol.PluginStatusParams( analysis: protocol.AnalysisStatus(false), ).toNotification(), ); } } Future _handleRequest(Request request) async { var requestTime = DateTime.now().millisecondsSinceEpoch; var id = request.id; Response? response; try { response = await _getResponse(request, requestTime); } on RequestFailure catch (exception) { response = Response(id, requestTime, error: exception.error); } catch (exception, stackTrace) { response = Response( id, requestTime, error: protocol.RequestError( protocol.RequestErrorCode.PLUGIN_ERROR, exception.toString(), stackTrace: stackTrace.toString(), ), ); } if (response != null) { _channel.sendResponse(response); } } Future _handleRequestZoned(Request request) async { await runZonedGuarded(() => _handleRequest(request), (error, stackTrace) { _channel.sendNotification( protocol.PluginErrorParams( false /* isFatal */, error.toString(), stackTrace.toString(), ).toNotification(), ); }); } bool _isPriorityAnalysisContext(AnalysisContext analysisContext) => _priorityPaths.any(analysisContext.contextRoot.isAnalyzed); static protocol.Location _locationFor( CompilationUnit unit, String path, Diagnostic diagnostic, ) { var lineInfo = unit.lineInfo; var startLocation = lineInfo.getLocation(diagnostic.offset); var endLocation = lineInfo.getLocation( diagnostic.offset + diagnostic.length, ); return protocol.Location( path, diagnostic.offset, diagnostic.length, startLocation.lineNumber, startLocation.columnNumber, endLine: endLocation.lineNumber, endColumn: endLocation.columnNumber, ); } }