Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library analysis.server; | 5 library analysis.server; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/analysis_logger.dart'; | 10 import 'package:analysis_server/src/analysis_logger.dart'; |
| 11 import 'package:analysis_server/src/channel.dart'; | 11 import 'package:analysis_server/src/channel.dart'; |
| 12 import 'package:analysis_server/src/constants.dart'; | 12 import 'package:analysis_server/src/constants.dart'; |
| 13 import 'package:analysis_server/src/context_directory_manager.dart'; | 13 import 'package:analysis_server/src/context_directory_manager.dart'; |
| 14 import 'package:analysis_server/src/domain_analysis.dart'; | 14 import 'package:analysis_server/src/domain_analysis.dart'; |
| 15 import 'package:analysis_server/src/operation/operation.dart'; | |
| 15 import 'package:analysis_server/src/operation/operation_analysis.dart'; | 16 import 'package:analysis_server/src/operation/operation_analysis.dart'; |
| 16 import 'package:analysis_server/src/operation/operation.dart'; | |
| 17 import 'package:analysis_server/src/operation/operation_queue.dart'; | 17 import 'package:analysis_server/src/operation/operation_queue.dart'; |
| 18 import 'package:analysis_server/src/package_map_provider.dart'; | 18 import 'package:analysis_server/src/package_map_provider.dart'; |
| 19 import 'package:analysis_server/src/package_uri_resolver.dart'; | 19 import 'package:analysis_server/src/package_uri_resolver.dart'; |
| 20 import 'package:analysis_server/src/protocol.dart'; | 20 import 'package:analysis_server/src/protocol.dart'; |
| 21 import 'package:analysis_server/src/resource.dart'; | 21 import 'package:analysis_server/src/resource.dart'; |
| 22 import 'package:analyzer/src/generated/ast.dart'; | 22 import 'package:analyzer/src/generated/ast.dart'; |
| 23 import 'package:analyzer/src/generated/engine.dart'; | 23 import 'package:analyzer/src/generated/engine.dart'; |
| 24 import 'package:analyzer/src/generated/error.dart'; | 24 import 'package:analyzer/src/generated/error.dart'; |
| 25 import 'package:analyzer/src/generated/source.dart'; | 25 import 'package:analyzer/src/generated/index.dart'; |
| 26 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 26 import 'package:analyzer/src/generated/sdk.dart'; | 27 import 'package:analyzer/src/generated/sdk.dart'; |
| 27 import 'package:analyzer/src/generated/sdk_io.dart'; | 28 import 'package:analyzer/src/generated/sdk_io.dart'; |
| 29 import 'package:analyzer/src/generated/source.dart'; | |
| 28 import 'package:analyzer/src/generated/source_io.dart'; | 30 import 'package:analyzer/src/generated/source_io.dart'; |
| 29 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 30 import 'package:analyzer/src/generated/index.dart'; | |
| 31 | 31 |
| 32 | 32 |
| 33 /** | 33 /** |
| 34 * An instance of [DirectoryBasedDartSdk] that is shared between | 34 * An instance of [DirectoryBasedDartSdk] that is shared between |
| 35 * [AnalysisServer] instances to improve performance. | 35 * [AnalysisServer] instances to improve performance. |
| 36 */ | 36 */ |
| 37 final DirectoryBasedDartSdk SHARED_SDK = DirectoryBasedDartSdk.defaultSdk; | 37 final DirectoryBasedDartSdk SHARED_SDK = DirectoryBasedDartSdk.defaultSdk; |
| 38 | 38 |
| 39 class AnalysisServerContextDirectoryManager extends ContextDirectoryManager { | 39 typedef void OptionUpdater(AnalysisOptionsImpl options); |
| 40 final AnalysisServer analysisServer; | |
| 41 | |
| 42 /** | |
| 43 * The default options used to create new analysis contexts. | |
| 44 */ | |
| 45 AnalysisOptionsImpl defaultOptions = new AnalysisOptionsImpl(); | |
| 46 | |
| 47 AnalysisServerContextDirectoryManager( | |
| 48 this.analysisServer, ResourceProvider resourceProvider, | |
| 49 PackageMapProvider packageMapProvider) | |
| 50 : super(resourceProvider, packageMapProvider); | |
| 51 | |
| 52 @override | |
| 53 void addContext(Folder folder, Map<String, List<Folder>> packageMap) { | |
| 54 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); | |
| 55 analysisServer.folderMap[folder] = context; | |
| 56 context.sourceFactory = _createSourceFactory(packageMap); | |
| 57 context.analysisOptions = new AnalysisOptionsImpl.con1(defaultOptions); | |
| 58 analysisServer.schedulePerformAnalysisOperation(context); | |
| 59 } | |
| 60 | |
| 61 @override | |
| 62 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) { | |
| 63 AnalysisContext context = analysisServer.folderMap[contextFolder]; | |
| 64 context.applyChanges(changeSet); | |
| 65 analysisServer.schedulePerformAnalysisOperation(context); | |
| 66 } | |
| 67 | |
| 68 @override | |
| 69 void removeContext(Folder folder) { | |
| 70 analysisServer.folderMap.remove(folder); | |
| 71 } | |
| 72 | |
| 73 @override | |
| 74 void updateContextPackageMap(Folder contextFolder, | |
| 75 Map<String, List<Folder>> packageMap) { | |
| 76 AnalysisContext context = analysisServer.folderMap[contextFolder]; | |
| 77 context.sourceFactory = _createSourceFactory(packageMap); | |
| 78 analysisServer.schedulePerformAnalysisOperation(context); | |
| 79 } | |
| 80 | |
| 81 /** | |
| 82 * Set up a [SourceFactory] that resolves packages using the given | |
| 83 * [packageMap]. | |
| 84 */ | |
| 85 SourceFactory _createSourceFactory(Map<String, List<Folder>> packageMap) { | |
| 86 List<UriResolver> resolvers = <UriResolver>[ | |
| 87 new DartUriResolver(analysisServer.defaultSdk), | |
| 88 new ResourceUriResolver(resourceProvider), | |
| 89 new PackageMapUriResolver(resourceProvider, packageMap) | |
| 90 ]; | |
| 91 return new SourceFactory(resolvers); | |
| 92 } | |
| 93 } | |
| 94 | 40 |
| 95 /** | 41 /** |
| 96 * Instances of the class [AnalysisServer] implement a server that listens on a | 42 * Instances of the class [AnalysisServer] implement a server that listens on a |
| 97 * [CommunicationChannel] for analysis requests and process them. | 43 * [CommunicationChannel] for analysis requests and process them. |
| 98 */ | 44 */ |
| 99 class AnalysisServer { | 45 class AnalysisServer { |
| 100 /** | 46 /** |
| 101 * The channel from which requests are received and to which responses should | 47 * The channel from which requests are received and to which responses should |
| 102 * be sent. | 48 * be sent. |
| 103 */ | 49 */ |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 134 List<RequestHandler> handlers; | 80 List<RequestHandler> handlers; |
| 135 | 81 |
| 136 /** | 82 /** |
| 137 * The current default [DartSdk]. | 83 * The current default [DartSdk]. |
| 138 */ | 84 */ |
| 139 DartSdk defaultSdk = SHARED_SDK; | 85 DartSdk defaultSdk = SHARED_SDK; |
| 140 | 86 |
| 141 /** | 87 /** |
| 142 * A table mapping [Folder]s to the [AnalysisContext]s associated with them. | 88 * A table mapping [Folder]s to the [AnalysisContext]s associated with them. |
| 143 */ | 89 */ |
| 144 final Map<Folder, AnalysisContext> folderMap = | 90 final Map<Folder, AnalysisContext> folderMap = new HashMap<Folder, |
|
Brian Wilkerson
2014/07/02 13:58:06
You probably have no control, but I liked the old
scheglov
2014/07/02 17:33:09
Yes, we need to improve formatter.
Rolled back for
| |
| 145 new HashMap<Folder, AnalysisContext>(); | 91 AnalysisContext>(); |
| 146 | 92 |
| 147 /** | 93 /** |
| 148 * A queue of the operations to perform in this server. | 94 * A queue of the operations to perform in this server. |
| 149 * | 95 * |
| 150 * Invariant: when this queue is non-empty, there is exactly one pending call | 96 * Invariant: when this queue is non-empty, there is exactly one pending call |
| 151 * to [performOperation] on the event queue. When this list is empty, there a re | 97 * to [performOperation] on the event queue. When this list is empty, there a re |
| 152 * no calls to [performOperation] on the event queue. | 98 * no calls to [performOperation] on the event queue. |
| 153 */ | 99 */ |
| 154 ServerOperationQueue operationQueue; | 100 ServerOperationQueue operationQueue; |
| 155 | 101 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 174 /** | 120 /** |
| 175 * Initialize a newly created server to receive requests from and send | 121 * Initialize a newly created server to receive requests from and send |
| 176 * responses to the given [channel]. | 122 * responses to the given [channel]. |
| 177 * | 123 * |
| 178 * If [rethrowExceptions] is true, then any exceptions thrown by analysis are | 124 * If [rethrowExceptions] is true, then any exceptions thrown by analysis are |
| 179 * propagated up the call stack. The default is true to allow analysis | 125 * propagated up the call stack. The default is true to allow analysis |
| 180 * exceptions to show up in unit tests, but it should be set to false when | 126 * exceptions to show up in unit tests, but it should be set to false when |
| 181 * running a full analysis server. | 127 * running a full analysis server. |
| 182 */ | 128 */ |
| 183 AnalysisServer(this.channel, ResourceProvider resourceProvider, | 129 AnalysisServer(this.channel, ResourceProvider resourceProvider, |
| 184 PackageMapProvider packageMapProvider, this.index, | 130 PackageMapProvider packageMapProvider, this.index, {this.rethrowExceptions : |
|
Brian Wilkerson
2014/07/02 13:58:06
I liked the old formatting better here too.
| |
| 185 {this.rethrowExceptions: true}) { | 131 true}) { |
| 186 operationQueue = new ServerOperationQueue(this); | 132 operationQueue = new ServerOperationQueue(this); |
| 187 contextDirectoryManager = new AnalysisServerContextDirectoryManager( | 133 contextDirectoryManager = new AnalysisServerContextDirectoryManager(this, |
| 188 this, resourceProvider, packageMapProvider); | 134 resourceProvider, packageMapProvider); |
| 189 AnalysisEngine.instance.logger = new AnalysisLogger(); | 135 AnalysisEngine.instance.logger = new AnalysisLogger(); |
| 190 running = true; | 136 running = true; |
| 191 Notification notification = new Notification(SERVER_CONNECTED); | 137 Notification notification = new Notification(SERVER_CONNECTED); |
| 192 channel.sendNotification(notification); | 138 channel.sendNotification(notification); |
| 193 channel.listen(handleRequest, onDone: done, onError: error); | 139 channel.listen(handleRequest, onDone: done, onError: error); |
| 194 } | 140 } |
| 195 | 141 |
| 196 /** | 142 /** |
| 197 * Schedules execution of the given [ServerOperation]. | |
| 198 */ | |
| 199 void scheduleOperation(ServerOperation operation) { | |
| 200 bool wasEmpty = operationQueue.isEmpty; | |
| 201 addOperation(operation); | |
| 202 if (wasEmpty) { | |
| 203 _schedulePerformOperation(); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 /** | |
| 208 * Schedules analysis of the given context. | |
| 209 */ | |
| 210 void schedulePerformAnalysisOperation(AnalysisContext context) { | |
| 211 scheduleOperation(new PerformAnalysisOperation(context, false)); | |
| 212 } | |
| 213 | |
| 214 /** | |
| 215 * Send the given [notification] to the client. | |
| 216 */ | |
| 217 void sendNotification(Notification notification) { | |
| 218 channel.sendNotification(notification); | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * Set the priority files to the given [files]. | |
| 223 */ | |
| 224 void setPriorityFiles(Request request, List<String> files) { | |
| 225 Map<AnalysisContext, List<Source>> sourceMap = | |
| 226 new HashMap<AnalysisContext, List<Source>>(); | |
| 227 List<String> unanalyzed = new List<String>(); | |
| 228 files.forEach((file) { | |
| 229 AnalysisContext analysisContext = _getAnalysisContext(file); | |
| 230 if (analysisContext == null) { | |
| 231 unanalyzed.add(file); | |
| 232 } else { | |
| 233 List<Source> sourceList = sourceMap[analysisContext]; | |
| 234 if (sourceList == null) { | |
| 235 sourceList = <Source>[]; | |
| 236 sourceMap[analysisContext] = sourceList; | |
| 237 } | |
| 238 sourceList.add(_getSource(file)); | |
| 239 } | |
| 240 }); | |
| 241 if (unanalyzed.isNotEmpty) { | |
| 242 StringBuffer buffer = new StringBuffer(); | |
| 243 buffer.writeAll(unanalyzed, ', '); | |
| 244 throw new RequestFailure(new Response.unanalyzedPriorityFiles(request, | |
| 245 buffer.toString())); | |
| 246 } | |
| 247 folderMap.forEach((Folder folder, AnalysisContext context) { | |
| 248 List<Source> sourceList = sourceMap[context]; | |
| 249 if (sourceList == null) { | |
| 250 sourceList = Source.EMPTY_ARRAY; | |
| 251 } | |
| 252 context.analysisPriorityOrder = sourceList; | |
| 253 }); | |
| 254 } | |
| 255 | |
| 256 /** | |
| 257 * Use the given updaters to update the values of the options in every | |
| 258 * existing analysis context. | |
| 259 */ | |
| 260 void updateOptions(List<OptionUpdater> optionUpdaters) { | |
| 261 // | |
| 262 // Update existing contexts. | |
| 263 // | |
| 264 folderMap.forEach((Folder folder, AnalysisContext context) { | |
| 265 AnalysisOptionsImpl options = new AnalysisOptionsImpl.con1(context.analysi sOptions); | |
| 266 optionUpdaters.forEach((OptionUpdater optionUpdater) { | |
| 267 optionUpdater(options); | |
| 268 }); | |
| 269 context.analysisOptions = options; | |
| 270 }); | |
| 271 // | |
| 272 // Update the defaults used to create new contexts. | |
| 273 // | |
| 274 AnalysisOptionsImpl options = contextDirectoryManager.defaultOptions; | |
| 275 optionUpdaters.forEach((OptionUpdater optionUpdater) { | |
| 276 optionUpdater(options); | |
| 277 }); | |
| 278 } | |
| 279 | |
| 280 /** | |
| 281 * Adds the given [ServerOperation] to the queue, but does not schedule | 143 * Adds the given [ServerOperation] to the queue, but does not schedule |
| 282 * operations execution. | 144 * operations execution. |
| 283 */ | 145 */ |
| 284 void addOperation(ServerOperation operation) { | 146 void addOperation(ServerOperation operation) { |
| 285 operationQueue.add(operation); | 147 operationQueue.add(operation); |
| 286 } | 148 } |
| 287 | 149 |
| 288 /** | 150 /** |
| 289 * The socket from which requests are being read has been closed. | 151 * The socket from which requests are being read has been closed. |
| 290 */ | 152 */ |
| 291 void done() { | 153 void done() { |
| 292 index.stop(); | 154 index.stop(); |
| 293 running = false; | 155 running = false; |
| 294 } | 156 } |
| 295 | 157 |
| 296 /** | 158 /** |
| 297 * There was an error related to the socket from which requests are being | 159 * There was an error related to the socket from which requests are being |
| 298 * read. | 160 * read. |
| 299 */ | 161 */ |
| 300 void error(argument) { | 162 void error(argument) { |
| 301 running = false; | 163 running = false; |
| 302 } | 164 } |
| 303 | 165 |
| 304 // TODO(brianwilkerson) Add the following method after 'prioritySources' has | 166 /** |
| 305 // been added to InternalAnalysisContext. | 167 * Returns the [CompilationUnit] of the Dart file with the given [path] that |
| 306 // /** | 168 * should be used to resend notifications for already resolved unit. |
| 307 // * Return a list containing the full names of all of the sources that are | 169 * Returns `null` if the file is not a part of any context, library has not |
| 308 // * priority sources. | 170 * been yet resolved, or any problem happened. |
| 309 // */ | 171 */ |
| 310 // List<String> getPriorityFiles() { | 172 CompilationUnit getResolvedCompilationUnitToResendNotification(String path) { |
| 311 // List<String> priorityFiles = new List<String>(); | 173 // prepare AnalysisContext |
| 312 // folderMap.values.forEach((ContextDirectory directory) { | 174 AnalysisContext context = getAnalysisContext(path); |
| 313 // InternalAnalysisContext context = directory.context; | 175 if (context == null) { |
| 314 // context.prioritySources.forEach((Source source) { | 176 return null; |
| 315 // priorityFiles.add(source.fullName); | 177 } |
| 316 // }); | 178 // prepare sources |
| 317 // }); | 179 Source unitSource = getSource(path); |
| 318 // return priorityFiles; | 180 List<Source> librarySources = context.getLibrariesContaining(unitSource); |
| 319 // } | 181 if (librarySources.isEmpty) { |
| 182 return null; | |
| 183 } | |
| 184 // if library has not been resolved yet, the unit will be resolved later | |
| 185 Source librarySource = librarySources[0]; | |
| 186 if (context.getLibraryElement(librarySource) == null) { | |
| 187 return null; | |
| 188 } | |
| 189 // if library has been already resolved, resolve unit | |
| 190 return context.resolveCompilationUnit2(unitSource, librarySource); | |
| 191 } | |
| 320 | 192 |
| 321 /** | 193 /** |
| 322 * Handle a [request] that was read from the communication channel. | 194 * Handle a [request] that was read from the communication channel. |
| 323 */ | 195 */ |
| 324 void handleRequest(Request request) { | 196 void handleRequest(Request request) { |
| 325 int count = handlers.length; | 197 int count = handlers.length; |
| 326 for (int i = 0; i < count; i++) { | 198 for (int i = 0; i < count; i++) { |
| 327 try { | 199 try { |
| 328 Response response = handlers[i].handleRequest(request); | 200 Response response = handlers[i].handleRequest(request); |
| 329 if (response != null) { | 201 if (response != null) { |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 347 } | 219 } |
| 348 | 220 |
| 349 /** | 221 /** |
| 350 * Returns `true` if the given [AnalysisContext] is a priority one. | 222 * Returns `true` if the given [AnalysisContext] is a priority one. |
| 351 */ | 223 */ |
| 352 bool isPriorityContext(AnalysisContext context) { | 224 bool isPriorityContext(AnalysisContext context) { |
| 353 // TODO(scheglov) implement support for priority sources/contexts | 225 // TODO(scheglov) implement support for priority sources/contexts |
| 354 return false; | 226 return false; |
| 355 } | 227 } |
| 356 | 228 |
| 229 // TODO(brianwilkerson) Add the following method after 'prioritySources' has | |
| 230 // been added to InternalAnalysisContext. | |
| 231 // /** | |
| 232 // * Return a list containing the full names of all of the sources that are | |
| 233 // * priority sources. | |
| 234 // */ | |
| 235 // List<String> getPriorityFiles() { | |
| 236 // List<String> priorityFiles = new List<String>(); | |
| 237 // folderMap.values.forEach((ContextDirectory directory) { | |
| 238 // InternalAnalysisContext context = directory.context; | |
| 239 // context.prioritySources.forEach((Source source) { | |
| 240 // priorityFiles.add(source.fullName); | |
| 241 // }); | |
| 242 // }); | |
| 243 // return priorityFiles; | |
| 244 // } | |
| 245 | |
| 357 /** | 246 /** |
| 358 * Perform the next available [ServerOperation]. | 247 * Perform the next available [ServerOperation]. |
| 359 */ | 248 */ |
| 360 void performOperation() { | 249 void performOperation() { |
| 361 if (!running) { | 250 if (!running) { |
| 362 // An error has occurred, or the connection to the client has been | 251 // An error has occurred, or the connection to the client has been |
| 363 // closed, since this method was scheduled on the event queue. So | 252 // closed, since this method was scheduled on the event queue. So |
| 364 // don't do anything. Instead clear the operation queue. | 253 // don't do anything. Instead clear the operation queue. |
| 365 operationQueue.clear(); | 254 operationQueue.clear(); |
| 366 return; | 255 return; |
| 367 } | 256 } |
| 368 // prepare next operation | 257 // prepare next operation |
| 369 ServerOperation operation = operationQueue.take(); | 258 ServerOperation operation = operationQueue.take(); |
| 370 sendStatusNotification(operation); | 259 sendStatusNotification(operation); |
| 371 // perform the operation | 260 // perform the operation |
| 372 try { | 261 try { |
| 373 operation.perform(this); | 262 operation.perform(this); |
| 374 } catch (exception, stackTrace) { | 263 } catch (exception, stackTrace) { |
| 375 AnalysisEngine.instance.logger.logError("${exception}\n${stackTrace}"); | 264 AnalysisEngine.instance.logger.logError("${exception}\n${stackTrace}"); |
| 376 if (rethrowExceptions) { | 265 if (rethrowExceptions) { |
| 377 throw new AnalysisException( | 266 throw new AnalysisException('Unexpected exception during analysis', |
| 378 'Unexpected exception during analysis', | |
| 379 new CaughtException(exception, stackTrace)); | 267 new CaughtException(exception, stackTrace)); |
| 380 } | 268 } |
| 381 } finally { | 269 } finally { |
| 382 if (!operationQueue.isEmpty) { | 270 if (!operationQueue.isEmpty) { |
| 383 _schedulePerformOperation(); | 271 _schedulePerformOperation(); |
| 384 } else { | 272 } else { |
| 385 sendStatusNotification(null); | 273 sendStatusNotification(null); |
| 386 } | 274 } |
| 387 } | 275 } |
| 388 } | 276 } |
| 389 | 277 |
| 390 /** | 278 /** |
| 279 * Schedules execution of the given [ServerOperation]. | |
| 280 */ | |
| 281 void scheduleOperation(ServerOperation operation) { | |
| 282 bool wasEmpty = operationQueue.isEmpty; | |
| 283 addOperation(operation); | |
| 284 if (wasEmpty) { | |
| 285 _schedulePerformOperation(); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Schedules analysis of the given context. | |
| 291 */ | |
| 292 void schedulePerformAnalysisOperation(AnalysisContext context) { | |
| 293 scheduleOperation(new PerformAnalysisOperation(context, false)); | |
| 294 } | |
| 295 | |
| 296 /** | |
| 297 * Send the given [notification] to the client. | |
| 298 */ | |
| 299 void sendNotification(Notification notification) { | |
| 300 channel.sendNotification(notification); | |
| 301 } | |
| 302 | |
| 303 /** | |
| 391 * Send status notification to the client. The `operation` is the operation | 304 * Send status notification to the client. The `operation` is the operation |
| 392 * being performed or `null` if analysis is complete. | 305 * being performed or `null` if analysis is complete. |
| 393 */ | 306 */ |
| 394 void sendStatusNotification(ServerOperation operation) { | 307 void sendStatusNotification(ServerOperation operation) { |
| 395 // Only send status when it changes | 308 // Only send status when it changes |
| 396 bool isAnalyzing = operation != null; | 309 bool isAnalyzing = operation != null; |
| 397 if (statusAnalyzing == isAnalyzing) { | 310 if (statusAnalyzing == isAnalyzing) { |
| 398 return; | 311 return; |
| 399 } | 312 } |
| 400 statusAnalyzing = isAnalyzing; | 313 statusAnalyzing = isAnalyzing; |
| 401 Notification notification = new Notification(SERVER_STATUS); | 314 Notification notification = new Notification(SERVER_STATUS); |
| 402 Map<String, Object> analysis = new HashMap(); | 315 Map<String, Object> analysis = new HashMap(); |
| 403 analysis['analyzing'] = isAnalyzing; | 316 analysis['analyzing'] = isAnalyzing; |
| 404 notification.params['analysis'] = analysis; | 317 notification.params['analysis'] = analysis; |
| 405 channel.sendNotification(notification); | 318 channel.sendNotification(notification); |
| 406 } | 319 } |
| 407 | 320 |
| 408 /** | 321 /** |
| 409 * Implementation for `analysis.setAnalysisRoots`. | 322 * Implementation for `analysis.setAnalysisRoots`. |
| 410 * | 323 * |
| 411 * TODO(scheglov) implement complete projects/contexts semantics. | 324 * TODO(scheglov) implement complete projects/contexts semantics. |
| 412 * | 325 * |
| 413 * The current implementation is intentionally simplified and expected | 326 * The current implementation is intentionally simplified and expected |
| 414 * that only folders are given each given folder corresponds to the exactly | 327 * that only folders are given each given folder corresponds to the exactly |
| 415 * one context. | 328 * one context. |
| 416 * | 329 * |
| 417 * So, we can start working in parallel on adding services and improving | 330 * So, we can start working in parallel on adding services and improving |
| 418 * projects/contexts support. | 331 * projects/contexts support. |
| 419 */ | 332 */ |
| 420 void setAnalysisRoots(String requestId, | 333 void setAnalysisRoots(String requestId, List<String> includedPaths, |
| 421 List<String> includedPaths, | 334 List<String> excludedPaths) { |
| 422 List<String> excludedPaths) { | |
| 423 try { | 335 try { |
| 424 contextDirectoryManager.setRoots(includedPaths, excludedPaths); | 336 contextDirectoryManager.setRoots(includedPaths, excludedPaths); |
| 425 } on UnimplementedError catch (e) { | 337 } on UnimplementedError catch (e) { |
| 426 throw new RequestFailure( | 338 throw new RequestFailure(new Response.unsupportedFeature(requestId, |
| 427 new Response.unsupportedFeature( | 339 e.message)); |
| 428 requestId, e.message)); | |
| 429 } | 340 } |
| 430 } | 341 } |
| 431 | 342 |
| 432 /** | 343 /** |
| 433 * Implementation for `analysis.updateContent`. | |
| 434 */ | |
| 435 void updateContent(Map<String, ContentChange> changes) { | |
| 436 changes.forEach((file, change) { | |
| 437 AnalysisContext analysisContext = _getAnalysisContext(file); | |
| 438 // TODO(paulberry): handle the case where a file is referred to by more | |
| 439 // than one context (e.g package A depends on package B using a local | |
| 440 // path, user has both packages open for editing in separate contexts, | |
| 441 // and user modifies a file in package B). | |
| 442 if (analysisContext != null) { | |
| 443 Source source = _getSource(file); | |
| 444 if (change.offset == null) { | |
| 445 analysisContext.setContents(source, change.content); | |
| 446 } else { | |
| 447 analysisContext.setChangedContents(source, change.content, | |
| 448 change.offset, change.oldLength, change.newLength); | |
| 449 } | |
| 450 schedulePerformAnalysisOperation(analysisContext); | |
| 451 } | |
| 452 }); | |
| 453 } | |
| 454 | |
| 455 /** | |
| 456 * Implementation for `analysis.setSubscriptions`. | 344 * Implementation for `analysis.setSubscriptions`. |
| 457 */ | 345 */ |
| 458 void setAnalysisSubscriptions(Map<AnalysisService, Set<String>> subscriptions) { | 346 void setAnalysisSubscriptions(Map<AnalysisService, |
| 347 Set<String>> subscriptions) { | |
| 459 // send notifications for already analyzed sources | 348 // send notifications for already analyzed sources |
| 460 subscriptions.forEach((service, Set<String> newFiles) { | 349 subscriptions.forEach((service, Set<String> newFiles) { |
| 461 Set<String> oldFiles = analysisServices[service]; | 350 Set<String> oldFiles = analysisServices[service]; |
| 462 Set<String> todoFiles = oldFiles != null ? newFiles.difference(oldFiles) : newFiles; | 351 Set<String> todoFiles = oldFiles != null ? newFiles.difference(oldFiles) : |
| 352 newFiles; | |
| 463 for (String file in todoFiles) { | 353 for (String file in todoFiles) { |
| 464 Source source = _getSource(file); | 354 Source source = getSource(file); |
| 465 AnalysisContext context = _getAnalysisContext(file); | 355 AnalysisContext context = getAnalysisContext(file); |
| 466 // errors | 356 // errors |
| 467 if (service == AnalysisService.ERRORS) { | 357 if (service == AnalysisService.ERRORS) { |
| 468 LineInfo lineInfo = context.getLineInfo(source); | 358 LineInfo lineInfo = context.getLineInfo(source); |
| 469 List<AnalysisError> errors = context.getErrors(source).errors; | 359 List<AnalysisError> errors = context.getErrors(source).errors; |
| 470 sendAnalysisNotificationErrors(this, file, lineInfo, errors); | 360 sendAnalysisNotificationErrors(this, file, lineInfo, errors); |
| 471 } | 361 } |
| 472 // Dart unit notifications. | 362 // Dart unit notifications. |
| 473 if (AnalysisEngine.isDartFileName(file)) { | 363 if (AnalysisEngine.isDartFileName(file)) { |
| 474 CompilationUnit dartUnit = getResolvedCompilationUnitToResendNotificat ion(file); | 364 CompilationUnit dartUnit = |
| 365 getResolvedCompilationUnitToResendNotification(file); | |
| 475 if (dartUnit != null) { | 366 if (dartUnit != null) { |
| 476 switch (service) { | 367 switch (service) { |
| 477 case AnalysisService.HIGHLIGHTS: | 368 case AnalysisService.HIGHLIGHTS: |
| 478 sendAnalysisNotificationHighlights(this, file, dartUnit); | 369 sendAnalysisNotificationHighlights(this, file, dartUnit); |
| 479 break; | 370 break; |
| 480 case AnalysisService.NAVIGATION: | 371 case AnalysisService.NAVIGATION: |
| 481 // TODO(scheglov) consider support for one unit in 2+ libraries | 372 // TODO(scheglov) consider support for one unit in 2+ libraries |
| 482 sendAnalysisNotificationNavigation(this, file, dartUnit); | 373 sendAnalysisNotificationNavigation(this, file, dartUnit); |
| 483 break; | 374 break; |
| 484 case AnalysisService.OUTLINE: | 375 case AnalysisService.OUTLINE: |
| 485 sendAnalysisNotificationOutline(this, context, source, dartUnit) ; | 376 sendAnalysisNotificationOutline(this, context, source, |
| 377 dartUnit); | |
| 486 break; | 378 break; |
| 487 } | 379 } |
| 488 } | 380 } |
| 489 } | 381 } |
| 490 } | 382 } |
| 491 }); | 383 }); |
| 492 // remember new subscriptions | 384 // remember new subscriptions |
| 493 this.analysisServices = subscriptions; | 385 this.analysisServices = subscriptions; |
| 494 } | 386 } |
| 495 | 387 |
| 496 /** | 388 /** |
| 497 * Return the [AnalysisContext] that is used to analyze the given [path]. | 389 * Set the priority files to the given [files]. |
| 498 * Return `null` if there is no such context. | |
| 499 */ | 390 */ |
| 500 AnalysisContext _getAnalysisContext(String path) { | 391 void setPriorityFiles(Request request, List<String> files) { |
| 501 for (Folder folder in folderMap.keys) { | 392 Map<AnalysisContext, List<Source>> sourceMap = new HashMap<AnalysisContext, |
| 502 if (path.startsWith(folder.path)) { | 393 List<Source>>(); |
| 503 return folderMap[folder]; | 394 List<String> unanalyzed = new List<String>(); |
| 395 files.forEach((file) { | |
| 396 AnalysisContext analysisContext = getAnalysisContext(file); | |
| 397 if (analysisContext == null) { | |
| 398 unanalyzed.add(file); | |
| 399 } else { | |
| 400 List<Source> sourceList = sourceMap[analysisContext]; | |
| 401 if (sourceList == null) { | |
| 402 sourceList = <Source>[]; | |
| 403 sourceMap[analysisContext] = sourceList; | |
| 404 } | |
| 405 sourceList.add(getSource(file)); | |
| 504 } | 406 } |
| 407 }); | |
| 408 if (unanalyzed.isNotEmpty) { | |
| 409 StringBuffer buffer = new StringBuffer(); | |
| 410 buffer.writeAll(unanalyzed, ', '); | |
| 411 throw new RequestFailure(new Response.unanalyzedPriorityFiles(request, | |
| 412 buffer.toString())); | |
| 505 } | 413 } |
| 506 return null; | 414 folderMap.forEach((Folder folder, AnalysisContext context) { |
| 415 List<Source> sourceList = sourceMap[context]; | |
| 416 if (sourceList == null) { | |
| 417 sourceList = Source.EMPTY_ARRAY; | |
| 418 } | |
| 419 context.analysisPriorityOrder = sourceList; | |
| 420 }); | |
| 507 } | 421 } |
| 508 | 422 |
| 509 /** | 423 /** |
| 510 * Return the [Source] of the Dart file with the given [path]. | 424 * Return `true` if all operations have been performed in this [AnalysisServer ]. |
| 511 */ | 425 */ |
| 512 Source _getSource(String path) { | 426 bool test_areOperationsFinished() { |
| 513 File file = contextDirectoryManager.resourceProvider.getResource(path); | 427 return operationQueue.isEmpty; |
| 514 return file.createSource(UriKind.FILE_URI); | |
| 515 } | 428 } |
| 516 | 429 |
| 517 /** | 430 /** |
| 518 * Returns the [CompilationUnit] of the Dart file with the given [path] that | |
| 519 * should be used to resend notifications for already resolved unit. | |
| 520 * Returns `null` if the file is not a part of any context, library has not | |
| 521 * been yet resolved, or any problem happened. | |
| 522 */ | |
| 523 CompilationUnit getResolvedCompilationUnitToResendNotification(String path) { | |
| 524 // prepare AnalysisContext | |
| 525 AnalysisContext context = _getAnalysisContext(path); | |
| 526 if (context == null) { | |
| 527 return null; | |
| 528 } | |
| 529 // prepare sources | |
| 530 Source unitSource = _getSource(path); | |
| 531 List<Source> librarySources = context.getLibrariesContaining(unitSource); | |
| 532 if (librarySources.isEmpty) { | |
| 533 return null; | |
| 534 } | |
| 535 // if library has not been resolved yet, the unit will be resolved later | |
| 536 Source librarySource = librarySources[0]; | |
| 537 if (context.getLibraryElement(librarySource) == null) { | |
| 538 return null; | |
| 539 } | |
| 540 // if library has been already resolved, resolve unit | |
| 541 return context.resolveCompilationUnit2(unitSource, librarySource); | |
| 542 } | |
| 543 | |
| 544 /** | |
| 545 * Return the [CompilationUnit] of the Dart file with the given [path]. | 431 * Return the [CompilationUnit] of the Dart file with the given [path]. |
| 546 * Return `null` if the file is not a part of any context. | 432 * Return `null` if the file is not a part of any context. |
| 547 */ | 433 */ |
| 548 CompilationUnit test_getResolvedCompilationUnit(String path) { | 434 CompilationUnit test_getResolvedCompilationUnit(String path) { |
| 549 // prepare AnalysisContext | 435 // prepare AnalysisContext |
| 550 AnalysisContext context = _getAnalysisContext(path); | 436 AnalysisContext context = getAnalysisContext(path); |
| 551 if (context == null) { | 437 if (context == null) { |
| 552 return null; | 438 return null; |
| 553 } | 439 } |
| 554 // prepare sources | 440 // prepare sources |
| 555 Source unitSource = _getSource(path); | 441 Source unitSource = getSource(path); |
| 556 List<Source> librarySources = context.getLibrariesContaining(unitSource); | 442 List<Source> librarySources = context.getLibrariesContaining(unitSource); |
| 557 if (librarySources.isEmpty) { | 443 if (librarySources.isEmpty) { |
| 558 return null; | 444 return null; |
| 559 } | 445 } |
| 560 // get a resolved unit | 446 // get a resolved unit |
| 561 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]); | 447 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]); |
| 562 } | 448 } |
| 563 | 449 |
| 564 /** | 450 /** |
| 565 * Return `true` if all operations have been performed in this [AnalysisServer ]. | 451 * Implementation for `analysis.updateContent`. |
| 566 */ | 452 */ |
| 567 bool test_areOperationsFinished() { | 453 void updateContent(Map<String, ContentChange> changes) { |
| 568 return operationQueue.isEmpty; | 454 changes.forEach((file, change) { |
| 455 AnalysisContext analysisContext = getAnalysisContext(file); | |
| 456 // TODO(paulberry): handle the case where a file is referred to by more | |
| 457 // than one context (e.g package A depends on package B using a local | |
| 458 // path, user has both packages open for editing in separate contexts, | |
| 459 // and user modifies a file in package B). | |
| 460 if (analysisContext != null) { | |
| 461 Source source = getSource(file); | |
| 462 if (change.offset == null) { | |
| 463 analysisContext.setContents(source, change.content); | |
| 464 } else { | |
| 465 analysisContext.setChangedContents(source, change.content, | |
| 466 change.offset, change.oldLength, change.newLength); | |
| 467 } | |
| 468 schedulePerformAnalysisOperation(analysisContext); | |
| 469 } | |
| 470 }); | |
| 569 } | 471 } |
| 570 | 472 |
| 571 /** | 473 /** |
| 474 * Use the given updaters to update the values of the options in every | |
| 475 * existing analysis context. | |
| 476 */ | |
| 477 void updateOptions(List<OptionUpdater> optionUpdaters) { | |
| 478 // | |
| 479 // Update existing contexts. | |
| 480 // | |
| 481 folderMap.forEach((Folder folder, AnalysisContext context) { | |
| 482 AnalysisOptionsImpl options = new AnalysisOptionsImpl.con1( | |
| 483 context.analysisOptions); | |
| 484 optionUpdaters.forEach((OptionUpdater optionUpdater) { | |
| 485 optionUpdater(options); | |
| 486 }); | |
| 487 context.analysisOptions = options; | |
| 488 }); | |
| 489 // | |
| 490 // Update the defaults used to create new contexts. | |
| 491 // | |
| 492 AnalysisOptionsImpl options = contextDirectoryManager.defaultOptions; | |
| 493 optionUpdaters.forEach((OptionUpdater optionUpdater) { | |
| 494 optionUpdater(options); | |
| 495 }); | |
| 496 } | |
| 497 | |
| 498 /** | |
| 499 * Return the [AnalysisContext] that is used to analyze the given [path]. | |
| 500 * Return `null` if there is no such context. | |
| 501 */ | |
| 502 AnalysisContext getAnalysisContext(String path) { | |
| 503 for (Folder folder in folderMap.keys) { | |
| 504 if (path.startsWith(folder.path)) { | |
| 505 return folderMap[folder]; | |
| 506 } | |
| 507 } | |
| 508 return null; | |
| 509 } | |
| 510 | |
| 511 /** | |
| 512 * Return the [Source] of the Dart file with the given [path]. | |
| 513 */ | |
| 514 Source getSource(String path) { | |
| 515 File file = contextDirectoryManager.resourceProvider.getResource(path); | |
| 516 return file.createSource(UriKind.FILE_URI); | |
| 517 } | |
| 518 | |
| 519 /** | |
| 572 * Schedules [performOperation] exection. | 520 * Schedules [performOperation] exection. |
| 573 */ | 521 */ |
| 574 void _schedulePerformOperation() { | 522 void _schedulePerformOperation() { |
| 575 new Future(performOperation); | 523 new Future(performOperation); |
| 576 } | 524 } |
| 577 } | 525 } |
| 578 | 526 |
| 579 | 527 |
| 528 class AnalysisServerContextDirectoryManager extends ContextDirectoryManager { | |
| 529 final AnalysisServer analysisServer; | |
| 530 | |
| 531 /** | |
| 532 * The default options used to create new analysis contexts. | |
| 533 */ | |
| 534 AnalysisOptionsImpl defaultOptions = new AnalysisOptionsImpl(); | |
| 535 | |
| 536 AnalysisServerContextDirectoryManager(this.analysisServer, | |
| 537 ResourceProvider resourceProvider, PackageMapProvider packageMapProvider) | |
| 538 : super(resourceProvider, packageMapProvider); | |
| 539 | |
| 540 @override | |
| 541 void addContext(Folder folder, Map<String, List<Folder>> packageMap) { | |
| 542 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); | |
| 543 analysisServer.folderMap[folder] = context; | |
| 544 context.sourceFactory = _createSourceFactory(packageMap); | |
| 545 context.analysisOptions = new AnalysisOptionsImpl.con1(defaultOptions); | |
| 546 analysisServer.schedulePerformAnalysisOperation(context); | |
| 547 } | |
| 548 | |
| 549 @override | |
| 550 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) { | |
| 551 AnalysisContext context = analysisServer.folderMap[contextFolder]; | |
| 552 context.applyChanges(changeSet); | |
| 553 analysisServer.schedulePerformAnalysisOperation(context); | |
| 554 } | |
| 555 | |
| 556 @override | |
| 557 void removeContext(Folder folder) { | |
| 558 analysisServer.folderMap.remove(folder); | |
| 559 } | |
| 560 | |
| 561 @override | |
| 562 void updateContextPackageMap(Folder contextFolder, Map<String, | |
| 563 List<Folder>> packageMap) { | |
| 564 AnalysisContext context = analysisServer.folderMap[contextFolder]; | |
| 565 context.sourceFactory = _createSourceFactory(packageMap); | |
| 566 analysisServer.schedulePerformAnalysisOperation(context); | |
| 567 } | |
| 568 | |
| 569 /** | |
| 570 * Set up a [SourceFactory] that resolves packages using the given | |
| 571 * [packageMap]. | |
| 572 */ | |
| 573 SourceFactory _createSourceFactory(Map<String, List<Folder>> packageMap) { | |
| 574 List<UriResolver> resolvers = <UriResolver>[new DartUriResolver( | |
| 575 analysisServer.defaultSdk), new ResourceUriResolver(resourceProvider), | |
| 576 new PackageMapUriResolver(resourceProvider, packageMap)]; | |
| 577 return new SourceFactory(resolvers); | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 | |
| 580 /** | 582 /** |
| 581 * An enumeration of the services provided by the analysis domain. | 583 * An enumeration of the services provided by the analysis domain. |
| 582 */ | 584 */ |
| 583 class AnalysisService extends Enum2<AnalysisService> { | 585 class AnalysisService extends Enum2<AnalysisService> { |
| 584 static const AnalysisService ERRORS = const AnalysisService('ERRORS', 0); | 586 static const AnalysisService ERRORS = const AnalysisService('ERRORS', 0); |
| 585 static const AnalysisService HIGHLIGHTS = const AnalysisService('HIGHLIGHTS', 1); | 587 static const AnalysisService HIGHLIGHTS = const AnalysisService('HIGHLIGHTS', |
| 586 static const AnalysisService NAVIGATION = const AnalysisService('NAVIGATION', 2); | 588 1); |
| 589 static const AnalysisService NAVIGATION = const AnalysisService('NAVIGATION', | |
| 590 2); | |
| 587 static const AnalysisService OUTLINE = const AnalysisService('OUTLINE', 3); | 591 static const AnalysisService OUTLINE = const AnalysisService('OUTLINE', 3); |
| 588 | 592 |
| 589 static const List<AnalysisService> VALUES = | 593 static const List<AnalysisService> VALUES = const [ERRORS, HIGHLIGHTS, |
| 590 const [ERRORS, HIGHLIGHTS, NAVIGATION, OUTLINE]; | 594 NAVIGATION, OUTLINE]; |
| 591 | 595 |
| 592 const AnalysisService(String name, int ordinal) : super(name, ordinal); | 596 const AnalysisService(String name, int ordinal) : super(name, ordinal); |
| 593 } | 597 } |
| 594 | 598 |
| 595 | |
| 596 typedef void OptionUpdater(AnalysisOptionsImpl options); | |
| 597 | |
| 598 /** | 599 /** |
| 599 * An enumeration of the services provided by the server domain. | 600 * An enumeration of the services provided by the server domain. |
| 600 */ | 601 */ |
| 601 class ServerService extends Enum2<ServerService> { | 602 class ServerService extends Enum2<ServerService> { |
| 602 static const ServerService STATUS = const ServerService('STATUS', 0); | 603 static const ServerService STATUS = const ServerService('STATUS', 0); |
| 603 | 604 |
| 604 static const List<ServerService> VALUES = const [STATUS]; | 605 static const List<ServerService> VALUES = const [STATUS]; |
| 605 | 606 |
| 606 const ServerService(String name, int ordinal) : super(name, ordinal); | 607 const ServerService(String name, int ordinal) : super(name, ordinal); |
| 607 } | 608 } |
| OLD | NEW |