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