Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'package:analyzer/file_system/file_system.dart'; | |
| 6 import 'package:analyzer/src/dart/analysis/byte_store.dart'; | |
| 7 import 'package:analyzer/src/dart/analysis/driver.dart' | |
| 8 show AnalysisDriverScheduler, PerformanceLog; | |
| 9 import 'package:analyzer/src/dart/analysis/file_state.dart'; | |
| 10 import 'package:analyzer/src/generated/source.dart'; | |
| 11 import 'package:analyzer_plugin/channel/channel.dart'; | |
| 12 import 'package:analyzer_plugin/protocol/generated_protocol.dart'; | |
| 13 import 'package:analyzer_plugin/protocol/protocol.dart'; | |
| 14 import 'package:analyzer_plugin/protocol/protocol_constants.dart'; | |
| 15 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; | |
| 16 import 'package:analyzer_plugin/src/utilities/null_string_sink.dart'; | |
| 17 import 'package:analyzer_plugin/utilities/subscription_manager.dart'; | |
| 18 | |
| 19 /** | |
| 20 * The abstract superclass of any class implementing a plugin for the analysis | |
| 21 * server. | |
| 22 * | |
| 23 * Clients may not implement or mix-in this class, but are expected to extend | |
| 24 * it. | |
| 25 */ | |
| 26 abstract class ServerPlugin { | |
| 27 /** | |
| 28 * The communication channel being used to communicate with the analysis | |
| 29 * server. | |
| 30 */ | |
| 31 PluginCommunicationChannel _channel; | |
| 32 | |
| 33 /** | |
| 34 * The resource provider used to access the file system. | |
| 35 */ | |
| 36 final ResourceProvider resourceProvider; | |
| 37 | |
| 38 /** | |
| 39 * The object used to manage analysis subscriptions. | |
| 40 */ | |
| 41 final SubscriptionManager subscriptionManager = new SubscriptionManager(); | |
| 42 | |
| 43 /** | |
| 44 * The scheduler used by any analysis drivers that are created. | |
| 45 */ | |
| 46 AnalysisDriverScheduler analysisDriverScheduler; | |
| 47 | |
| 48 /** | |
| 49 * The performance log used by any analysis drivers that are created. | |
| 50 */ | |
| 51 final PerformanceLog performanceLog = | |
| 52 new PerformanceLog(new NullStringSink()); | |
| 53 | |
| 54 /** | |
| 55 * The byte store used by any analysis drivers that are created. | |
| 56 */ | |
| 57 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
| |
| 58 | |
| 59 /** | |
| 60 * The file content overlay used by any analysis drivers that are created. | |
| 61 */ | |
| 62 final FileContentOverlay fileContentOverlay = new FileContentOverlay(); | |
| 63 | |
| 64 /** | |
| 65 * Initialize a newly created analysis server plugin. If a resource [provider] | |
| 66 * is given, then it will be used to access the file system. Otherwise a | |
| 67 * resource provider that accesses the physical file system will be used. | |
| 68 */ | |
| 69 ServerPlugin(this.resourceProvider) { | |
| 70 analysisDriverScheduler = new AnalysisDriverScheduler(performanceLog); | |
| 71 } | |
| 72 | |
| 73 /** | |
| 74 * Return the communication channel being used to communicate with the | |
| 75 * analysis server, or `null` if the plugin has not been started. | |
| 76 */ | |
| 77 PluginCommunicationChannel get channel => _channel; | |
| 78 | |
| 79 /** | |
| 80 * Handle the fact that the file with the given [path] has been modified. | |
| 81 */ | |
| 82 void contentChanged(String path) { | |
| 83 // Ignore changes to files. | |
| 84 } | |
| 85 | |
| 86 /** | |
| 87 * Handle an 'analysis.handleWatchEvents' request. | |
| 88 */ | |
| 89 AnalysisHandleWatchEventsResult handleAnalysisHandleWatchEvents( | |
| 90 Map<String, Object> parameters) => | |
| 91 null; | |
| 92 | |
| 93 /** | |
| 94 * Handle an 'analysis.reanalyze' request. | |
| 95 */ | |
| 96 AnalysisReanalyzeResult handleAnalysisReanalyze( | |
| 97 Map<String, Object> parameters) => | |
| 98 null; | |
| 99 | |
| 100 /** | |
| 101 * Handle an 'analysis.setContextBuilderOptions' request. | |
| 102 */ | |
| 103 AnalysisSetContextBuilderOptionsResult handleAnalysisSetContextBuilderOptions( | |
| 104 Map<String, Object> parameters) => | |
| 105 null; | |
| 106 | |
| 107 /** | |
| 108 * Handle an 'analysis.setContextRoots' request. | |
| 109 */ | |
| 110 AnalysisSetContextRootsResult handleAnalysisSetContextRoots( | |
| 111 Map<String, Object> parameters) { | |
| 112 // TODO(brianwilkerson) Implement this so that implementors don't have to | |
| 113 // figure out how to manage contexts. | |
| 114 return null; | |
| 115 } | |
| 116 | |
| 117 /** | |
| 118 * Handle an 'analysis.setPriorityFiles' request. | |
| 119 */ | |
| 120 AnalysisSetPriorityFilesResult handleAnalysisSetPriorityFiles( | |
| 121 Map<String, Object> parameters) => | |
| 122 new AnalysisSetPriorityFilesResult(); | |
| 123 | |
| 124 /** | |
| 125 * Handle an 'analysis.setSubscriptions' request. Most subclasses should not | |
| 126 * override this method, but should instead use the [subscriptionManager] to | |
| 127 * access the list of subscriptions for any given file. | |
| 128 */ | |
| 129 AnalysisSetSubscriptionsResult handleAnalysisSetSubscriptions( | |
| 130 Map<String, Object> parameters) { | |
| 131 Map<AnalysisService, List<String>> subscriptions = validateParameter( | |
| 132 parameters, | |
| 133 ANALYSIS_REQUEST_SET_SUBSCRIPTIONS_SUBSCRIPTIONS, | |
| 134 'analysis.setSubscriptions'); | |
| 135 subscriptionManager.setSubscriptions(subscriptions); | |
| 136 // TODO(brianwilkerson) Cause any newly subscribed for notifications to be s ent. | |
| 137 return new AnalysisSetSubscriptionsResult(); | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * Handle an 'analysis.updateContent' request. Most subclasses should not | |
| 142 * override this method, but should instead use the [contentCache] to access | |
| 143 * the current content of overlaid files. | |
| 144 */ | |
| 145 AnalysisUpdateContentResult handleAnalysisUpdateContent( | |
| 146 Map<String, Object> parameters) { | |
| 147 Map<String, Object> files = validateParameter(parameters, | |
| 148 ANALYSIS_REQUEST_UPDATE_CONTENT_FILES, 'analysis.updateContent'); | |
| 149 files.forEach((String filePath, Object overlay) { | |
| 150 // We don't need to get the correct URI because only the full path is | |
| 151 // used by the contentCache. | |
| 152 Source source = resourceProvider.getFile(filePath).createSource(); | |
| 153 if (overlay is AddContentOverlay) { | |
| 154 fileContentOverlay[source.fullName] = overlay.content; | |
| 155 } else if (overlay is ChangeContentOverlay) { | |
| 156 String fileName = source.fullName; | |
| 157 String oldContents = fileContentOverlay[fileName]; | |
| 158 String newContents; | |
| 159 if (oldContents == null) { | |
| 160 // The server should only send a ChangeContentOverlay if there is | |
| 161 // already an existing overlay for the source. | |
| 162 throw new RequestFailure(new RequestError( | |
| 163 RequestErrorCode.INVALID_OVERLAY_CHANGE, | |
| 164 'Invalid overlay change: no content to change')); | |
| 165 } | |
| 166 try { | |
| 167 newContents = SourceEdit.applySequence(oldContents, overlay.edits); | |
| 168 } on RangeError { | |
| 169 throw new RequestFailure(new RequestError( | |
| 170 RequestErrorCode.INVALID_OVERLAY_CHANGE, | |
| 171 'Invalid overlay change: invalid edit')); | |
| 172 } | |
| 173 fileContentOverlay[fileName] = newContents; | |
| 174 } else if (overlay is RemoveContentOverlay) { | |
| 175 fileContentOverlay[source.fullName] = null; | |
| 176 } | |
| 177 contentChanged(filePath); | |
| 178 }); | |
| 179 return new AnalysisUpdateContentResult(); | |
| 180 } | |
| 181 | |
| 182 /** | |
| 183 * Handle a 'completion.getSuggestions' request. | |
| 184 */ | |
| 185 CompletionGetSuggestionsResult handleCompletionGetSuggestions( | |
| 186 Map<String, Object> parameters) => | |
| 187 new CompletionGetSuggestionsResult( | |
| 188 -1, -1, const <CompletionSuggestion>[]); | |
| 189 | |
| 190 /** | |
| 191 * Handle an 'edit.getAssists' request. | |
| 192 */ | |
| 193 EditGetAssistsResult handleEditGetAssists(Map<String, Object> parameters) => | |
| 194 new EditGetAssistsResult(const <SourceChange>[]); | |
| 195 | |
| 196 /** | |
| 197 * Handle an 'edit.getAvailableRefactorings' request. Subclasses that override | |
| 198 * this method in order to participate in refactorings must also override the | |
| 199 * method [handleEditGetRefactoring]. | |
| 200 */ | |
| 201 EditGetAvailableRefactoringsResult handleEditGetAvailableRefactorings( | |
| 202 Map<String, Object> parameters) => | |
| 203 new EditGetAvailableRefactoringsResult(const <RefactoringKind>[]); | |
| 204 | |
| 205 /** | |
| 206 * Handle an 'edit.getFixes' request. | |
| 207 */ | |
| 208 EditGetFixesResult handleEditGetFixes(Map<String, Object> parameters) => | |
| 209 new EditGetFixesResult(const <AnalysisErrorFixes>[]); | |
| 210 | |
| 211 /** | |
| 212 * Handle an 'edit.getRefactoring' request. | |
| 213 */ | |
| 214 EditGetRefactoringResult handleEditGetRefactoring( | |
| 215 Map<String, Object> parameters) => | |
| 216 null; | |
| 217 | |
| 218 /** | |
| 219 * Handle a 'plugin.shutdown' request. Subclasses can override this method to | |
| 220 * perform any required clean-up, but cannot prevent the plugin from shutting | |
| 221 * down. | |
| 222 */ | |
| 223 PluginShutdownResult handlePluginShutdown(Map<String, Object> parameters) => | |
| 224 new PluginShutdownResult(); | |
| 225 | |
| 226 /** | |
| 227 * Handle a 'plugin.versionCheck' request. | |
| 228 */ | |
| 229 PluginVersionCheckResult handlePluginVersionCheck( | |
| 230 Map<String, Object> parameters); | |
| 231 | |
| 232 /** | |
| 233 * The method that is called when the analysis server closes the communication | |
| 234 * channel. This method will not be invoked under normal conditions because | |
| 235 * the server will send a shutdown request and the plugin will stop listening | |
| 236 * to the channel before the server closes the channel. | |
| 237 */ | |
| 238 void onDone() {} | |
| 239 | |
| 240 /** | |
| 241 * The method that is called when an error has occurred in the analysis | |
| 242 * server. This method will not be invoked under normal conditions. | |
| 243 */ | |
| 244 void onError(Object exception, StackTrace stackTrace) {} | |
| 245 | |
| 246 /** | |
| 247 * Start this plugin by listening to the given communication [channel]. | |
| 248 */ | |
| 249 void start(PluginCommunicationChannel channel) { | |
| 250 this._channel = channel; | |
| 251 _channel.listen(_onRequest, onError: onError, onDone: onDone); | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * Validate that the value in the map of [parameters] at the given [key] is of | |
| 256 * the type [T]. If it is, return it. Otherwise throw a [RequestFailure] that | |
| 257 * will cause an error to be returned to the server. | |
| 258 */ | |
| 259 Object/*=T*/ validateParameter/*<T>*/( | |
| 260 Map<String, Object> parameters, String key, String requestName) { | |
| 261 Object value = parameters[key]; | |
| 262 // ignore: type_annotation_generic_function_parameter | |
| 263 if (value is Object/*=T*/) { | |
| 264 return value; | |
| 265 } | |
| 266 String message; | |
| 267 if (value == null) { | |
| 268 message = 'Missing parameter $key in $requestName'; | |
| 269 } else { | |
| 270 message = 'Invalid value for $key in $requestName (${value.runtimeType})'; | |
| 271 } | |
| 272 throw new RequestFailure( | |
| 273 new RequestError(RequestErrorCode.INVALID_PARAMETER, message)); | |
| 274 } | |
| 275 | |
| 276 /** | |
| 277 * Compute the response that should be returned for the given [request], or | |
| 278 * `null` if the response has already been sent. | |
| 279 */ | |
| 280 Response _getResponse(Request request) { | |
| 281 ResponseResult result = null; | |
| 282 switch (request.id) { | |
| 283 case ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS: | |
| 284 result = handleAnalysisHandleWatchEvents(request.params); | |
| 285 break; | |
| 286 case ANALYSIS_REQUEST_REANALYZE: | |
| 287 result = handleAnalysisReanalyze(request.params); | |
| 288 break; | |
| 289 case ANALYSIS_REQUEST_SET_CONTEXT_BUILDER_OPTIONS: | |
| 290 result = handleAnalysisSetContextBuilderOptions(request.params); | |
| 291 break; | |
| 292 case ANALYSIS_REQUEST_SET_CONTEXT_ROOTS: | |
| 293 result = handleAnalysisSetContextRoots(request.params); | |
| 294 break; | |
| 295 case ANALYSIS_REQUEST_SET_PRIORITY_FILES: | |
| 296 result = handleAnalysisSetPriorityFiles(request.params); | |
| 297 break; | |
| 298 case ANALYSIS_REQUEST_SET_SUBSCRIPTIONS: | |
| 299 result = handleAnalysisSetSubscriptions(request.params); | |
| 300 break; | |
| 301 case ANALYSIS_REQUEST_UPDATE_CONTENT: | |
| 302 result = handleAnalysisUpdateContent(request.params); | |
| 303 break; | |
| 304 case COMPLETION_REQUEST_GET_SUGGESTIONS: | |
| 305 result = handleCompletionGetSuggestions(request.params); | |
| 306 break; | |
| 307 case EDIT_REQUEST_GET_ASSISTS: | |
| 308 result = handleEditGetAssists(request.params); | |
| 309 break; | |
| 310 case EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS: | |
| 311 result = handleEditGetAvailableRefactorings(request.params); | |
| 312 break; | |
| 313 case EDIT_REQUEST_GET_FIXES: | |
| 314 result = handleEditGetFixes(request.params); | |
| 315 break; | |
| 316 case EDIT_REQUEST_GET_REFACTORING: | |
| 317 result = handleEditGetRefactoring(request.params); | |
| 318 break; | |
| 319 case PLUGIN_REQUEST_SHUTDOWN: | |
| 320 result = handlePluginShutdown(request.params); | |
| 321 _channel.sendResponse(result.toResponse(request.id)); | |
| 322 _channel.close(); | |
| 323 return null; | |
| 324 case PLUGIN_REQUEST_VERSION_CHECK: | |
| 325 result = handlePluginVersionCheck(request.params); | |
| 326 break; | |
| 327 } | |
| 328 if (result == null) { | |
| 329 return new Response(request.id, | |
| 330 error: RequestErrorFactory.unknownRequest(request)); | |
| 331 } | |
| 332 return result.toResponse(request.id); | |
| 333 } | |
| 334 | |
| 335 /** | |
| 336 * The method that is called when a [request] is received from the analysis | |
| 337 * server. | |
| 338 */ | |
| 339 void _onRequest(Request request) { | |
| 340 String id = request.id; | |
| 341 Response response; | |
| 342 try { | |
| 343 response = _getResponse(request); | |
| 344 } on RequestFailure catch (exception) { | |
| 345 _channel.sendResponse(new Response(id, error: exception.error)); | |
| 346 } catch (exception, stackTrace) { | |
| 347 response = new Response(id, | |
| 348 error: new RequestError( | |
| 349 RequestErrorCode.PLUGIN_ERROR, exception.toString(), | |
| 350 stackTrace: stackTrace.toString())); | |
| 351 } | |
| 352 if (response != null) { | |
| 353 _channel.sendResponse(response); | |
| 354 } | |
| 355 } | |
| 356 } | |
| OLD | NEW |