Chromium Code Reviews| Index: pkg/analyzer_plugin/lib/plugin/plugin.dart |
| diff --git a/pkg/analyzer_plugin/lib/plugin/plugin.dart b/pkg/analyzer_plugin/lib/plugin/plugin.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..195e0ca245aa9a99fd37f4d1aabeb4abb857cb8c |
| --- /dev/null |
| +++ b/pkg/analyzer_plugin/lib/plugin/plugin.dart |
| @@ -0,0 +1,356 @@ |
| +// Copyright (c) 2017, 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 'package:analyzer/file_system/file_system.dart'; |
| +import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| +import 'package:analyzer/src/dart/analysis/driver.dart' |
| + show AnalysisDriverScheduler, PerformanceLog; |
| +import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| +import 'package:analyzer/src/generated/source.dart'; |
| +import 'package:analyzer_plugin/channel/channel.dart'; |
| +import 'package:analyzer_plugin/protocol/generated_protocol.dart'; |
| +import 'package:analyzer_plugin/protocol/protocol.dart'; |
| +import 'package:analyzer_plugin/protocol/protocol_constants.dart'; |
| +import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; |
| +import 'package:analyzer_plugin/src/utilities/null_string_sink.dart'; |
| +import 'package:analyzer_plugin/utilities/subscription_manager.dart'; |
| + |
| +/** |
| + * The abstract superclass of any class implementing a plugin for the analysis |
| + * server. |
| + * |
| + * Clients may not implement or mix-in this class, but are expected to extend |
| + * it. |
| + */ |
| +abstract class ServerPlugin { |
| + /** |
| + * The communication channel being used to communicate with the analysis |
| + * server. |
| + */ |
| + PluginCommunicationChannel _channel; |
| + |
| + /** |
| + * The resource provider used to access the file system. |
| + */ |
| + final ResourceProvider resourceProvider; |
| + |
| + /** |
| + * The object used to manage analysis subscriptions. |
| + */ |
| + final SubscriptionManager subscriptionManager = new SubscriptionManager(); |
| + |
| + /** |
| + * The scheduler used by any analysis drivers that are created. |
| + */ |
| + AnalysisDriverScheduler analysisDriverScheduler; |
| + |
| + /** |
| + * The performance log used by any analysis drivers that are created. |
| + */ |
| + final PerformanceLog performanceLog = |
| + new PerformanceLog(new NullStringSink()); |
| + |
| + /** |
| + * The byte store used by any analysis drivers that are created. |
| + */ |
| + final ByteStore byteStore = new MemoryByteStore(); |
|
scheglov
2017/01/31 21:27:02
Will it be injected?
I guess we want to share anal
Brian Wilkerson
2017/01/31 21:37:09
I assume you mean that the on-disk data should be
scheglov
2017/01/31 21:41:17
SGTM
|
| + |
| + /** |
| + * The file content overlay used by any analysis drivers that are created. |
| + */ |
| + final FileContentOverlay fileContentOverlay = new FileContentOverlay(); |
| + |
| + /** |
| + * Initialize a newly created analysis server plugin. If a resource [provider] |
| + * is given, then it will be used to access the file system. Otherwise a |
| + * resource provider that accesses the physical file system will be used. |
| + */ |
| + ServerPlugin(this.resourceProvider) { |
| + analysisDriverScheduler = new AnalysisDriverScheduler(performanceLog); |
| + } |
| + |
| + /** |
| + * Return the communication channel being used to communicate with the |
| + * analysis server, or `null` if the plugin has not been started. |
| + */ |
| + PluginCommunicationChannel get channel => _channel; |
| + |
| + /** |
| + * Handle the fact that the file with the given [path] has been modified. |
| + */ |
| + void contentChanged(String path) { |
| + // Ignore changes to files. |
| + } |
| + |
| + /** |
| + * Handle an 'analysis.handleWatchEvents' request. |
| + */ |
| + AnalysisHandleWatchEventsResult handleAnalysisHandleWatchEvents( |
| + Map<String, Object> parameters) => |
| + null; |
| + |
| + /** |
| + * Handle an 'analysis.reanalyze' request. |
| + */ |
| + AnalysisReanalyzeResult handleAnalysisReanalyze( |
| + Map<String, Object> parameters) => |
| + null; |
| + |
| + /** |
| + * Handle an 'analysis.setContextBuilderOptions' request. |
| + */ |
| + AnalysisSetContextBuilderOptionsResult handleAnalysisSetContextBuilderOptions( |
| + Map<String, Object> parameters) => |
| + null; |
| + |
| + /** |
| + * Handle an 'analysis.setContextRoots' request. |
| + */ |
| + AnalysisSetContextRootsResult handleAnalysisSetContextRoots( |
| + Map<String, Object> parameters) { |
| + // TODO(brianwilkerson) Implement this so that implementors don't have to |
| + // figure out how to manage contexts. |
| + return null; |
| + } |
| + |
| + /** |
| + * Handle an 'analysis.setPriorityFiles' request. |
| + */ |
| + AnalysisSetPriorityFilesResult handleAnalysisSetPriorityFiles( |
| + Map<String, Object> parameters) => |
| + new AnalysisSetPriorityFilesResult(); |
| + |
| + /** |
| + * Handle an 'analysis.setSubscriptions' request. Most subclasses should not |
| + * override this method, but should instead use the [subscriptionManager] to |
| + * access the list of subscriptions for any given file. |
| + */ |
| + AnalysisSetSubscriptionsResult handleAnalysisSetSubscriptions( |
| + Map<String, Object> parameters) { |
| + Map<AnalysisService, List<String>> subscriptions = validateParameter( |
| + parameters, |
| + ANALYSIS_REQUEST_SET_SUBSCRIPTIONS_SUBSCRIPTIONS, |
| + 'analysis.setSubscriptions'); |
| + subscriptionManager.setSubscriptions(subscriptions); |
| + // TODO(brianwilkerson) Cause any newly subscribed for notifications to be sent. |
| + return new AnalysisSetSubscriptionsResult(); |
| + } |
| + |
| + /** |
| + * Handle an 'analysis.updateContent' request. Most subclasses should not |
| + * override this method, but should instead use the [contentCache] to access |
| + * the current content of overlaid files. |
| + */ |
| + AnalysisUpdateContentResult handleAnalysisUpdateContent( |
| + Map<String, Object> parameters) { |
| + Map<String, Object> files = validateParameter(parameters, |
| + ANALYSIS_REQUEST_UPDATE_CONTENT_FILES, 'analysis.updateContent'); |
| + files.forEach((String filePath, Object overlay) { |
| + // We don't need to get the correct URI because only the full path is |
| + // used by the contentCache. |
| + Source source = resourceProvider.getFile(filePath).createSource(); |
| + if (overlay is AddContentOverlay) { |
| + fileContentOverlay[source.fullName] = overlay.content; |
| + } else if (overlay is ChangeContentOverlay) { |
| + String fileName = source.fullName; |
| + String oldContents = fileContentOverlay[fileName]; |
| + String newContents; |
| + if (oldContents == null) { |
| + // The server should only send a ChangeContentOverlay if there is |
| + // already an existing overlay for the source. |
| + throw new RequestFailure(new RequestError( |
| + RequestErrorCode.INVALID_OVERLAY_CHANGE, |
| + 'Invalid overlay change: no content to change')); |
| + } |
| + try { |
| + newContents = SourceEdit.applySequence(oldContents, overlay.edits); |
| + } on RangeError { |
| + throw new RequestFailure(new RequestError( |
| + RequestErrorCode.INVALID_OVERLAY_CHANGE, |
| + 'Invalid overlay change: invalid edit')); |
| + } |
| + fileContentOverlay[fileName] = newContents; |
| + } else if (overlay is RemoveContentOverlay) { |
| + fileContentOverlay[source.fullName] = null; |
| + } |
| + contentChanged(filePath); |
| + }); |
| + return new AnalysisUpdateContentResult(); |
| + } |
| + |
| + /** |
| + * Handle a 'completion.getSuggestions' request. |
| + */ |
| + CompletionGetSuggestionsResult handleCompletionGetSuggestions( |
| + Map<String, Object> parameters) => |
| + new CompletionGetSuggestionsResult( |
| + -1, -1, const <CompletionSuggestion>[]); |
| + |
| + /** |
| + * Handle an 'edit.getAssists' request. |
| + */ |
| + EditGetAssistsResult handleEditGetAssists(Map<String, Object> parameters) => |
| + new EditGetAssistsResult(const <SourceChange>[]); |
| + |
| + /** |
| + * Handle an 'edit.getAvailableRefactorings' request. Subclasses that override |
| + * this method in order to participate in refactorings must also override the |
| + * method [handleEditGetRefactoring]. |
| + */ |
| + EditGetAvailableRefactoringsResult handleEditGetAvailableRefactorings( |
| + Map<String, Object> parameters) => |
| + new EditGetAvailableRefactoringsResult(const <RefactoringKind>[]); |
| + |
| + /** |
| + * Handle an 'edit.getFixes' request. |
| + */ |
| + EditGetFixesResult handleEditGetFixes(Map<String, Object> parameters) => |
| + new EditGetFixesResult(const <AnalysisErrorFixes>[]); |
| + |
| + /** |
| + * Handle an 'edit.getRefactoring' request. |
| + */ |
| + EditGetRefactoringResult handleEditGetRefactoring( |
| + Map<String, Object> parameters) => |
| + null; |
| + |
| + /** |
| + * Handle a 'plugin.shutdown' request. Subclasses can override this method to |
| + * perform any required clean-up, but cannot prevent the plugin from shutting |
| + * down. |
| + */ |
| + PluginShutdownResult handlePluginShutdown(Map<String, Object> parameters) => |
| + new PluginShutdownResult(); |
| + |
| + /** |
| + * Handle a 'plugin.versionCheck' request. |
| + */ |
| + PluginVersionCheckResult handlePluginVersionCheck( |
| + Map<String, Object> parameters); |
| + |
| + /** |
| + * The method that is called when the analysis server closes the communication |
| + * channel. This method will not be invoked under normal conditions because |
| + * the server will send a shutdown request and the plugin will stop listening |
| + * to the channel before the server closes the channel. |
| + */ |
| + void onDone() {} |
| + |
| + /** |
| + * The method that is called when an error has occurred in the analysis |
| + * server. This method will not be invoked under normal conditions. |
| + */ |
| + void onError(Object exception, StackTrace stackTrace) {} |
| + |
| + /** |
| + * Start this plugin by listening to the given communication [channel]. |
| + */ |
| + void start(PluginCommunicationChannel channel) { |
| + this._channel = channel; |
| + _channel.listen(_onRequest, onError: onError, onDone: onDone); |
| + } |
| + |
| + /** |
| + * Validate that the value in the map of [parameters] at the given [key] is of |
| + * the type [T]. If it is, return it. Otherwise throw a [RequestFailure] that |
| + * will cause an error to be returned to the server. |
| + */ |
| + Object/*=T*/ validateParameter/*<T>*/( |
| + Map<String, Object> parameters, String key, String requestName) { |
| + Object value = parameters[key]; |
| + // ignore: type_annotation_generic_function_parameter |
| + if (value is Object/*=T*/) { |
| + return value; |
| + } |
| + String message; |
| + if (value == null) { |
| + message = 'Missing parameter $key in $requestName'; |
| + } else { |
| + message = 'Invalid value for $key in $requestName (${value.runtimeType})'; |
| + } |
| + throw new RequestFailure( |
| + new RequestError(RequestErrorCode.INVALID_PARAMETER, message)); |
| + } |
| + |
| + /** |
| + * Compute the response that should be returned for the given [request], or |
| + * `null` if the response has already been sent. |
| + */ |
| + Response _getResponse(Request request) { |
| + ResponseResult result = null; |
| + switch (request.id) { |
| + case ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS: |
| + result = handleAnalysisHandleWatchEvents(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_REANALYZE: |
| + result = handleAnalysisReanalyze(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_SET_CONTEXT_BUILDER_OPTIONS: |
| + result = handleAnalysisSetContextBuilderOptions(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_SET_CONTEXT_ROOTS: |
| + result = handleAnalysisSetContextRoots(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_SET_PRIORITY_FILES: |
| + result = handleAnalysisSetPriorityFiles(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_SET_SUBSCRIPTIONS: |
| + result = handleAnalysisSetSubscriptions(request.params); |
| + break; |
| + case ANALYSIS_REQUEST_UPDATE_CONTENT: |
| + result = handleAnalysisUpdateContent(request.params); |
| + break; |
| + case COMPLETION_REQUEST_GET_SUGGESTIONS: |
| + result = handleCompletionGetSuggestions(request.params); |
| + break; |
| + case EDIT_REQUEST_GET_ASSISTS: |
| + result = handleEditGetAssists(request.params); |
| + break; |
| + case EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS: |
| + result = handleEditGetAvailableRefactorings(request.params); |
| + break; |
| + case EDIT_REQUEST_GET_FIXES: |
| + result = handleEditGetFixes(request.params); |
| + break; |
| + case EDIT_REQUEST_GET_REFACTORING: |
| + result = handleEditGetRefactoring(request.params); |
| + break; |
| + case PLUGIN_REQUEST_SHUTDOWN: |
| + result = handlePluginShutdown(request.params); |
| + _channel.sendResponse(result.toResponse(request.id)); |
| + _channel.close(); |
| + return null; |
| + case PLUGIN_REQUEST_VERSION_CHECK: |
| + result = handlePluginVersionCheck(request.params); |
| + break; |
| + } |
| + if (result == null) { |
| + return new Response(request.id, |
| + error: RequestErrorFactory.unknownRequest(request)); |
| + } |
| + return result.toResponse(request.id); |
| + } |
| + |
| + /** |
| + * The method that is called when a [request] is received from the analysis |
| + * server. |
| + */ |
| + void _onRequest(Request request) { |
| + String id = request.id; |
| + Response response; |
| + try { |
| + response = _getResponse(request); |
| + } on RequestFailure catch (exception) { |
| + _channel.sendResponse(new Response(id, error: exception.error)); |
| + } catch (exception, stackTrace) { |
| + response = new Response(id, |
| + error: new RequestError( |
| + RequestErrorCode.PLUGIN_ERROR, exception.toString(), |
| + stackTrace: stackTrace.toString())); |
| + } |
| + if (response != null) { |
| + _channel.sendResponse(response); |
| + } |
| + } |
| +} |