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 |