| 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 | 8 |
| 9 import 'package:analysis_server/src/analysis_logger.dart'; | 9 import 'package:analysis_server/src/analysis_logger.dart'; |
| 10 import 'package:analysis_server/src/channel.dart'; | 10 import 'package:analysis_server/src/channel.dart'; |
| 11 import 'package:analysis_server/src/protocol.dart'; | 11 import 'package:analysis_server/src/protocol.dart'; |
| 12 import 'package:analysis_server/src/resource.dart'; |
| 13 import 'package:analyzer/src/generated/ast.dart'; |
| 12 import 'package:analyzer/src/generated/engine.dart'; | 14 import 'package:analyzer/src/generated/engine.dart'; |
| 13 import 'package:analyzer/src/generated/error.dart'; | 15 import 'package:analyzer/src/generated/error.dart'; |
| 14 import 'package:analyzer/src/generated/java_core.dart'; | 16 import 'package:analyzer/src/generated/java_core.dart'; |
| 17 import 'package:analyzer/src/generated/sdk.dart'; |
| 18 import 'package:analyzer/src/generated/sdk_io.dart'; |
| 19 import 'package:analyzer/src/generated/source_io.dart'; |
| 15 | 20 |
| 16 /** | 21 /** |
| 17 * Instances of the class [AnalysisServer] implement a server that listens on a | 22 * Instances of the class [AnalysisServer] implement a server that listens on a |
| 18 * [CommunicationChannel] for analysis requests and process them. | 23 * [CommunicationChannel] for analysis requests and process them. |
| 19 */ | 24 */ |
| 20 class AnalysisServer { | 25 class AnalysisServer { |
| 21 /** | 26 /** |
| 22 * The name of the notification of new errors associated with a source. | 27 * The name of the notification of new errors associated with a source. |
| 23 */ | 28 */ |
| 24 static const String ERROR_NOTIFICATION_NAME = 'context.errors'; | 29 static const String ERROR_NOTIFICATION_NAME = 'context.errors'; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 43 */ | 48 */ |
| 44 static const String CONNECTED_NOTIFICATION = 'server.connected'; | 49 static const String CONNECTED_NOTIFICATION = 'server.connected'; |
| 45 | 50 |
| 46 /** | 51 /** |
| 47 * The channel from which requests are received and to which responses should | 52 * The channel from which requests are received and to which responses should |
| 48 * be sent. | 53 * be sent. |
| 49 */ | 54 */ |
| 50 final ServerCommunicationChannel channel; | 55 final ServerCommunicationChannel channel; |
| 51 | 56 |
| 52 /** | 57 /** |
| 58 * The [ResourceProvider] using which paths are converted into [Resource]s. |
| 59 */ |
| 60 final ResourceProvider resourceProvider; |
| 61 |
| 62 /** |
| 53 * A flag indicating whether the server is running. When false, contexts | 63 * A flag indicating whether the server is running. When false, contexts |
| 54 * will no longer be added to [contextWorkQueue], and [performTask] will | 64 * will no longer be added to [contextWorkQueue], and [performTask] will |
| 55 * discard any tasks it finds on [contextWorkQueue]. | 65 * discard any tasks it finds on [contextWorkQueue]. |
| 56 */ | 66 */ |
| 57 bool running; | 67 bool running; |
| 58 | 68 |
| 59 /** | 69 /** |
| 60 * A list of the request handlers used to handle the requests sent to this | 70 * A list of the request handlers used to handle the requests sent to this |
| 61 * server. | 71 * server. |
| 62 */ | 72 */ |
| 63 List<RequestHandler> handlers; | 73 List<RequestHandler> handlers; |
| 64 | 74 |
| 65 /** | 75 // TODO(scheglov) remove once setAnalysisRoots() is completely implemented |
| 66 * A table mapping context id's to the analysis contexts associated with them. | 76 // /** |
| 67 */ | 77 // * A table mapping context id's to the analysis contexts associated with the
m. |
| 68 final Map<String, AnalysisContext> contextMap = new Map<String, AnalysisContex
t>(); | 78 // */ |
| 79 // final Map<String, AnalysisContext> contextMap = new Map<String, AnalysisCont
ext>(); |
| 80 // |
| 81 // /** |
| 82 // * A table mapping analysis contexts to the context id's associated with the
m. |
| 83 // */ |
| 84 // final Map<AnalysisContext, String> contextIdMap = new Map<AnalysisContext, S
tring>(); |
| 69 | 85 |
| 70 /** | 86 /** |
| 71 * A table mapping analysis contexts to the context id's associated with them. | 87 * The current default [DartSdk]. |
| 72 */ | 88 */ |
| 73 final Map<AnalysisContext, String> contextIdMap = new Map<AnalysisContext, Str
ing>(); | 89 DartSdk defaultSdk = DirectoryBasedDartSdk.defaultSdk; |
| 90 |
| 91 /** |
| 92 * A table mapping [Folder]s to the [PubFolder]s associated with them. |
| 93 */ |
| 94 final Map<Folder, PubFolder> folderMap = <Folder, PubFolder>{}; |
| 74 | 95 |
| 75 /** | 96 /** |
| 76 * A list of the analysis contexts for which analysis work needs to be | 97 * A list of the analysis contexts for which analysis work needs to be |
| 77 * performed. | 98 * performed. |
| 78 * | 99 * |
| 79 * Invariant: when this list is non-empty, there is exactly one pending call | 100 * Invariant: when this list is non-empty, there is exactly one pending call |
| 80 * to [performTask] on the event queue. When this list is empty, there are | 101 * to [performTask] on the event queue. When this list is empty, there are |
| 81 * no calls to [performTask] on the event queue. | 102 * no calls to [performTask] on the event queue. |
| 82 */ | 103 */ |
| 83 final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>(); | 104 final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>(); |
| 84 | 105 |
| 85 /** | 106 /** |
| 86 * A set of the [ServerService]s to send notifications for. | 107 * A set of the [ServerService]s to send notifications for. |
| 87 */ | 108 */ |
| 88 Set<ServerService> serverServices = new Set<ServerService>(); | 109 Set<ServerService> serverServices = new Set<ServerService>(); |
| 89 | 110 |
| 90 /** | 111 /** |
| 91 * Initialize a newly created server to receive requests from and send | 112 * Initialize a newly created server to receive requests from and send |
| 92 * responses to the given [channel]. | 113 * responses to the given [channel]. |
| 93 */ | 114 */ |
| 94 AnalysisServer(this.channel) { | 115 AnalysisServer(this.channel, this.resourceProvider) { |
| 95 AnalysisEngine.instance.logger = new AnalysisLogger(); | 116 AnalysisEngine.instance.logger = new AnalysisLogger(); |
| 96 running = true; | 117 running = true; |
| 97 Notification notification = new Notification(CONNECTED_NOTIFICATION); | 118 Notification notification = new Notification(CONNECTED_NOTIFICATION); |
| 98 channel.sendNotification(notification); | 119 channel.sendNotification(notification); |
| 99 channel.listen(handleRequest, onDone: done, onError: error); | 120 channel.listen(handleRequest, onDone: done, onError: error); |
| 100 } | 121 } |
| 101 | 122 |
| 102 /** | 123 /** |
| 103 * If [running] is true, add the given [context] to the list of analysis | 124 * If [running] is true, add the given [context] to the list of analysis |
| 104 * contexts for which analysis work needs to be performed, and ensure that | 125 * contexts for which analysis work needs to be performed, and ensure that |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 165 contextWorkQueue.clear(); | 186 contextWorkQueue.clear(); |
| 166 } | 187 } |
| 167 if (contextWorkQueue.isEmpty) { | 188 if (contextWorkQueue.isEmpty) { |
| 168 // Nothing to do. | 189 // Nothing to do. |
| 169 return; | 190 return; |
| 170 } | 191 } |
| 171 // | 192 // |
| 172 // Look for a context that has work to be done and then perform one task. | 193 // Look for a context that has work to be done and then perform one task. |
| 173 // | 194 // |
| 174 List<ChangeNotice> notices = null; | 195 List<ChangeNotice> notices = null; |
| 175 String contextId; | 196 // String contextId; |
| 176 try { | 197 try { |
| 177 AnalysisContext context = contextWorkQueue[0]; | 198 AnalysisContext context = contextWorkQueue[0]; |
| 178 contextId = contextIdMap[context]; | 199 // contextId = contextIdMap[context]; |
| 179 AnalysisResult result = context.performAnalysisTask(); | 200 AnalysisResult result = context.performAnalysisTask(); |
| 180 notices = result.changeNotices; | 201 notices = result.changeNotices; |
| 181 } finally { | 202 } finally { |
| 182 if (notices == null) { | 203 if (notices == null) { |
| 183 // Either we have no more work to do for this context, or there was an | 204 // Either we have no more work to do for this context, or there was an |
| 184 // unhandled exception trying to perform the analysis. In either case, | 205 // unhandled exception trying to perform the analysis. In either case, |
| 185 // remove the context form the work queue so we won't try to do more | 206 // remove the context form the work queue so we won't try to do more |
| 186 // analysis on it. | 207 // analysis on it. |
| 187 contextWorkQueue.removeAt(0); | 208 contextWorkQueue.removeAt(0); |
| 188 } | 209 } |
| 189 // | 210 // |
| 190 // Schedule this method to be run again if there is any more work to be | 211 // Schedule this method to be run again if there is any more work to be |
| 191 // done. | 212 // done. |
| 192 // | 213 // |
| 193 if (!contextWorkQueue.isEmpty) { | 214 if (!contextWorkQueue.isEmpty) { |
| 194 _scheduleTask(); | 215 _scheduleTask(); |
| 195 } | 216 } |
| 196 } | 217 } |
| 197 if (notices != null) { | 218 // TODO(scheglov) implement for [PubFolder] |
| 198 sendNotices(contextId, notices); | 219 // if (notices != null) { |
| 220 // sendNotices(contextId, notices); |
| 221 // } |
| 222 } |
| 223 |
| 224 // TODO(scheglov) rewrite for the new API. |
| 225 // /** |
| 226 // * Send the information in the given list of notices back to the client. |
| 227 // */ |
| 228 // void sendNotices(String contextId, List<ChangeNotice> notices) { |
| 229 // for (int i = 0; i < notices.length; i++) { |
| 230 // ChangeNotice notice = notices[i]; |
| 231 // Notification notification = new Notification(ERROR_NOTIFICATION_NAME); |
| 232 // notification.setParameter(CONTEXT_ID_PARAM, contextId); |
| 233 // notification.setParameter(SOURCE_PARAM, notice.source.encoding); |
| 234 // notification.setParameter(ERRORS_PARAM, notice.errors.map( |
| 235 // errorToJson).toList()); |
| 236 // sendNotification(notification); |
| 237 // } |
| 238 // } |
| 239 |
| 240 /** |
| 241 * Implementation for `server.setAnalysisRoots`. |
| 242 * |
| 243 * TODO(scheglov) implement complete projects/contexts semantics. |
| 244 * |
| 245 * The current implementation is intentionally simplified and expected |
| 246 * that only folders are given each given folder corresponds to the exactly |
| 247 * one context. |
| 248 * |
| 249 * So, we can start working in parallel on adding services and improving |
| 250 * projects/contexts support. |
| 251 */ |
| 252 void setAnalysisRoots(String requestId, |
| 253 List<String> includedPaths, |
| 254 List<String> excludedPaths) { |
| 255 // included |
| 256 Set<Folder> includedFolders = new Set<Folder>(); |
| 257 for (int i = 0; i < includedPaths.length; i++) { |
| 258 String path = includedPaths[i]; |
| 259 Resource resource = resourceProvider.getResource(path); |
| 260 if (resource is Folder) { |
| 261 includedFolders.add(resource); |
| 262 } else { |
| 263 // TODO(scheglov) implemented separate files analysis |
| 264 throw new RequestFailure( |
| 265 new Response.unsupportedFeature( |
| 266 requestId, |
| 267 '$path is not a folder. ' |
| 268 'Only support for folder analysis is implemented currently.')); |
| 269 } |
| 270 } |
| 271 // excluded |
| 272 // TODO(scheglov) remove when implemented |
| 273 if (excludedPaths.isNotEmpty) { |
| 274 throw new RequestFailure( |
| 275 new Response.unsupportedFeature( |
| 276 requestId, |
| 277 'Excluded paths are not supported yet')); |
| 278 } |
| 279 Set<Folder> excludedFolders = new Set<Folder>(); |
| 280 // diff |
| 281 Set<Folder> currentFolders = new Set<Folder>.from(folderMap.keys); |
| 282 Set<Folder> newFolders = includedFolders.difference(currentFolders); |
| 283 Set<Folder> oldFolders = currentFolders.difference(includedFolders); |
| 284 // remove old contexts |
| 285 for (Folder folder in oldFolders) { |
| 286 // TODO(scheglov) implement |
| 287 } |
| 288 // add new contexts |
| 289 for (Folder folder in newFolders) { |
| 290 PubFolder pubFolder = new PubFolder(defaultSdk, folder); |
| 291 folderMap[folder] = pubFolder; |
| 292 addContextToWorkQueue(pubFolder.context); |
| 199 } | 293 } |
| 200 } | 294 } |
| 201 | 295 |
| 202 /** | 296 /** |
| 203 * Send the information in the given list of notices back to the client. | 297 * Return the [AnalysisContext] that is used to analyze the given [path]. |
| 298 * Return `null` if there is no such context. |
| 204 */ | 299 */ |
| 205 void sendNotices(String contextId, List<ChangeNotice> notices) { | 300 AnalysisContext test_getAnalysisContext(String path) { |
| 206 for (int i = 0; i < notices.length; i++) { | 301 for (Folder folder in folderMap.keys) { |
| 207 ChangeNotice notice = notices[i]; | 302 if (path.startsWith(folder.fullName)) { |
| 208 Notification notification = new Notification(ERROR_NOTIFICATION_NAME); | 303 return folderMap[folder].context; |
| 209 notification.setParameter(CONTEXT_ID_PARAM, contextId); | 304 } |
| 210 notification.setParameter(SOURCE_PARAM, notice.source.encoding); | |
| 211 notification.setParameter(ERRORS_PARAM, notice.errors.map( | |
| 212 errorToJson).toList()); | |
| 213 sendNotification(notification); | |
| 214 } | 305 } |
| 306 return null; |
| 307 } |
| 308 |
| 309 /** |
| 310 * Return the [CompilationUnit] of the Dart file with the given [path]. |
| 311 * Return `null` if the file is not a part of any context. |
| 312 */ |
| 313 CompilationUnit test_getResolvedCompilationUnit(String path) { |
| 314 // prepare AnalysisContext |
| 315 AnalysisContext context = test_getAnalysisContext(path); |
| 316 if (context == null) { |
| 317 return null; |
| 318 } |
| 319 // prepare sources |
| 320 File file = resourceProvider.getResource(path); |
| 321 Source unitSource = file.createSource(UriKind.FILE_URI); |
| 322 List<Source> librarySources = context.getLibrariesContaining(unitSource); |
| 323 if (librarySources.isEmpty) { |
| 324 return null; |
| 325 } |
| 326 // get a resolved unit |
| 327 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]); |
| 215 } | 328 } |
| 216 | 329 |
| 217 static Map<String, Object> errorToJson(AnalysisError analysisError) { | 330 static Map<String, Object> errorToJson(AnalysisError analysisError) { |
| 218 // TODO(paulberry): move this function into the AnalysisError class. | 331 // TODO(paulberry): move this function into the AnalysisError class. |
| 219 | 332 |
| 220 // TODO(paulberry): we really shouldn't be exposing errorCode.ordinal | 333 // TODO(paulberry): we really shouldn't be exposing errorCode.ordinal |
| 221 // outside the analyzer, since the ordinal numbers change whenever we | 334 // outside the analyzer, since the ordinal numbers change whenever we |
| 222 // regenerate the analysis engine. | 335 // regenerate the analysis engine. |
| 223 Map<String, Object> result = { | 336 Map<String, Object> result = { |
| 224 'source': analysisError.source.encoding, | 337 'source': analysisError.source.encoding, |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 258 static const AnalysisService OUTLINE = const AnalysisService('OUTLINE', 3); | 371 static const AnalysisService OUTLINE = const AnalysisService('OUTLINE', 3); |
| 259 | 372 |
| 260 static const List<AnalysisService> VALUES = | 373 static const List<AnalysisService> VALUES = |
| 261 const [ERRORS, HIGHLIGHTS, NAVIGATION, OUTLINE]; | 374 const [ERRORS, HIGHLIGHTS, NAVIGATION, OUTLINE]; |
| 262 | 375 |
| 263 const AnalysisService(String name, int ordinal) : super(name, ordinal); | 376 const AnalysisService(String name, int ordinal) : super(name, ordinal); |
| 264 } | 377 } |
| 265 | 378 |
| 266 | 379 |
| 267 /** | 380 /** |
| 381 * Instances of [PubFolder] represents a [Folder] with a Pub `pubspec.yaml`. |
| 382 * |
| 383 * TODO(scheglov) implement complete projects/contexts semantics. |
| 384 * |
| 385 * This class is intentionally simplified to serve as a base to start working |
| 386 * on services while work on complete semantics is being done in parallel. |
| 387 */ |
| 388 class PubFolder { |
| 389 /** |
| 390 * The root [Folder] of this [PubFolder]. |
| 391 */ |
| 392 final Folder _folder; |
| 393 |
| 394 /** |
| 395 * The `pubspec.yaml` file in [_folder]. |
| 396 */ |
| 397 File _pubspecFile; |
| 398 |
| 399 /** |
| 400 * The [AnalysisContext] of this [_folder]. |
| 401 */ |
| 402 AnalysisContext _context; |
| 403 |
| 404 PubFolder(DartSdk sdk, this._folder) { |
| 405 // prepare pubspec.yaml |
| 406 _pubspecFile = _folder.getChild('pubspec.yaml'); |
| 407 if (!_pubspecFile.exists) { |
| 408 throw new ArgumentError('$_pubspecFile does not exist'); |
| 409 } |
| 410 // create AnalysisContext |
| 411 _context = AnalysisEngine.instance.createAnalysisContext(); |
| 412 // TODO(scheglov) replace FileUriResolver with an Resource based resolver |
| 413 // TODO(scheglov) create packages resolver |
| 414 _context.sourceFactory = new SourceFactory([ |
| 415 new DartUriResolver(sdk), |
| 416 new FileUriResolver(), |
| 417 // new PackageUriResolver(), |
| 418 ]); |
| 419 // add folder files |
| 420 { |
| 421 ChangeSet changeSet = new ChangeSet(); |
| 422 _addSourceFiles(changeSet, _folder); |
| 423 _context.applyChanges(changeSet); |
| 424 } |
| 425 } |
| 426 |
| 427 /** |
| 428 * Return the [AnalysisContext] of this folder. |
| 429 */ |
| 430 AnalysisContext get context => _context; |
| 431 |
| 432 /** |
| 433 * Resursively adds all Dart and HTML files to the [changeSet]. |
| 434 */ |
| 435 static void _addSourceFiles(ChangeSet changeSet, Folder folder) { |
| 436 List<Resource> children = folder.getChildren(); |
| 437 for (Resource child in children) { |
| 438 if (child is File) { |
| 439 String fileName = child.shortName; |
| 440 if (AnalysisEngine.isDartFileName(fileName) |
| 441 || AnalysisEngine.isHtmlFileName(fileName)) { |
| 442 Source source = child.createSource(UriKind.FILE_URI); |
| 443 changeSet.addedSource(source); |
| 444 } |
| 445 } else if (child is Folder) { |
| 446 _addSourceFiles(changeSet, child); |
| 447 } |
| 448 } |
| 449 } |
| 450 } |
| 451 |
| 452 |
| 453 /** |
| 268 * An enumeration of the services provided by the server domain. | 454 * An enumeration of the services provided by the server domain. |
| 269 */ | 455 */ |
| 270 class ServerService extends Enum2<ServerService> { | 456 class ServerService extends Enum2<ServerService> { |
| 271 static const ServerService STATUS = const ServerService('STATUS', 0); | 457 static const ServerService STATUS = const ServerService('STATUS', 0); |
| 272 | 458 |
| 273 static const List<ServerService> VALUES = const [STATUS]; | 459 static const List<ServerService> VALUES = const [STATUS]; |
| 274 | 460 |
| 275 const ServerService(String name, int ordinal) : super(name, ordinal); | 461 const ServerService(String name, int ordinal) : super(name, ordinal); |
| 276 } | 462 } |
| OLD | NEW |