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 | 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 * A table mapping [Folder]s to the [PubFolder]s associated with them. |
| 72 */ | 88 */ |
| 73 final Map<AnalysisContext, String> contextIdMap = new Map<AnalysisContext, Str ing>(); | 89 final Map<Folder, PubFolder> folderMap = <Folder, PubFolder>{}; |
| 74 | 90 |
| 75 /** | 91 /** |
| 76 * A list of the analysis contexts for which analysis work needs to be | 92 * A list of the analysis contexts for which analysis work needs to be |
| 77 * performed. | 93 * performed. |
| 78 * | 94 * |
| 79 * Invariant: when this list is non-empty, there is exactly one pending call | 95 * 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 | 96 * to [performTask] on the event queue. When this list is empty, there are |
| 81 * no calls to [performTask] on the event queue. | 97 * no calls to [performTask] on the event queue. |
| 82 */ | 98 */ |
| 83 final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>(); | 99 final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>(); |
| 84 | 100 |
| 85 /** | 101 /** |
| 86 * A set of the [ServerService]s to send notifications for. | 102 * A set of the [ServerService]s to send notifications for. |
| 87 */ | 103 */ |
| 88 Set<ServerService> serverServices = new Set<ServerService>(); | 104 Set<ServerService> serverServices = new Set<ServerService>(); |
| 89 | 105 |
| 90 /** | 106 /** |
| 91 * Initialize a newly created server to receive requests from and send | 107 * Initialize a newly created server to receive requests from and send |
| 92 * responses to the given [channel]. | 108 * responses to the given [channel]. |
| 93 */ | 109 */ |
| 94 AnalysisServer(this.channel) { | 110 AnalysisServer(this.channel, this.resourceProvider) { |
| 95 AnalysisEngine.instance.logger = new AnalysisLogger(); | 111 AnalysisEngine.instance.logger = new AnalysisLogger(); |
| 96 running = true; | 112 running = true; |
| 97 Notification notification = new Notification(CONNECTED_NOTIFICATION); | 113 Notification notification = new Notification(CONNECTED_NOTIFICATION); |
| 98 channel.sendNotification(notification); | 114 channel.sendNotification(notification); |
| 99 channel.listen(handleRequest, onDone: done, onError: error); | 115 channel.listen(handleRequest, onDone: done, onError: error); |
| 100 } | 116 } |
| 101 | 117 |
| 102 /** | 118 /** |
| 103 * If [running] is true, add the given [context] to the list of analysis | 119 * 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 | 120 * 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(); | 181 contextWorkQueue.clear(); |
| 166 } | 182 } |
| 167 if (contextWorkQueue.isEmpty) { | 183 if (contextWorkQueue.isEmpty) { |
| 168 // Nothing to do. | 184 // Nothing to do. |
| 169 return; | 185 return; |
| 170 } | 186 } |
| 171 // | 187 // |
| 172 // Look for a context that has work to be done and then perform one task. | 188 // Look for a context that has work to be done and then perform one task. |
| 173 // | 189 // |
| 174 List<ChangeNotice> notices = null; | 190 List<ChangeNotice> notices = null; |
| 175 String contextId; | 191 // String contextId; |
| 176 try { | 192 try { |
| 177 AnalysisContext context = contextWorkQueue[0]; | 193 AnalysisContext context = contextWorkQueue[0]; |
| 178 contextId = contextIdMap[context]; | 194 // contextId = contextIdMap[context]; |
| 179 AnalysisResult result = context.performAnalysisTask(); | 195 AnalysisResult result = context.performAnalysisTask(); |
| 180 notices = result.changeNotices; | 196 notices = result.changeNotices; |
| 181 } finally { | 197 } finally { |
| 182 if (notices == null) { | 198 if (notices == null) { |
| 183 // Either we have no more work to do for this context, or there was an | 199 // 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, | 200 // 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 | 201 // remove the context form the work queue so we won't try to do more |
| 186 // analysis on it. | 202 // analysis on it. |
| 187 contextWorkQueue.removeAt(0); | 203 contextWorkQueue.removeAt(0); |
| 188 } | 204 } |
| 189 // | 205 // |
| 190 // Schedule this method to be run again if there is any more work to be | 206 // Schedule this method to be run again if there is any more work to be |
| 191 // done. | 207 // done. |
| 192 // | 208 // |
| 193 if (!contextWorkQueue.isEmpty) { | 209 if (!contextWorkQueue.isEmpty) { |
| 194 _scheduleTask(); | 210 _scheduleTask(); |
| 195 } | 211 } |
| 196 } | 212 } |
| 197 if (notices != null) { | 213 // TODO(scheglov) implement for [PubFolder] |
| 198 sendNotices(contextId, notices); | 214 // if (notices != null) { |
| 215 // sendNotices(contextId, notices); | |
| 216 // } | |
| 217 } | |
| 218 | |
| 219 // TODO(scheglov) rewrite for the new API. | |
| 220 // /** | |
| 221 // * Send the information in the given list of notices back to the client. | |
| 222 // */ | |
| 223 // void sendNotices(String contextId, List<ChangeNotice> notices) { | |
| 224 // for (int i = 0; i < notices.length; i++) { | |
| 225 // ChangeNotice notice = notices[i]; | |
| 226 // Notification notification = new Notification(ERROR_NOTIFICATION_NAME); | |
| 227 // notification.setParameter(CONTEXT_ID_PARAM, contextId); | |
| 228 // notification.setParameter(SOURCE_PARAM, notice.source.encoding); | |
| 229 // notification.setParameter(ERRORS_PARAM, notice.errors.map( | |
| 230 // errorToJson).toList()); | |
| 231 // sendNotification(notification); | |
| 232 // } | |
| 233 // } | |
| 234 | |
| 235 void setAnalysisRoots(Set<Folder> includedFolders, Set<Folder> excludedFolders ) { | |
|
Brian Wilkerson
2014/05/27 14:09:57
This doesn't look for nested pub folders.
scheglov
2014/05/27 18:24:24
Yes, and it is intentional.
I've added comment to
| |
| 236 Set<Folder> currentFolders = new Set<Folder>.from(folderMap.keys); | |
|
Paul Berry
2014/05/27 16:30:29
What about analysis roots that aren't folders?
| |
| 237 Set<Folder> newFolders = includedFolders.difference(currentFolders); | |
| 238 Set<Folder> oldFolders = currentFolders.difference(includedFolders); | |
| 239 // remove old contexts | |
| 240 for (Folder folder in oldFolders) { | |
| 241 // TODO(scheglov) implement | |
| 242 } | |
| 243 // add new contexts | |
| 244 for (Folder folder in newFolders) { | |
| 245 PubFolder pubFolder = new PubFolder(folder); | |
|
Brian Wilkerson
2014/05/27 14:09:57
What about analysis roots that do not contain a pu
| |
| 246 folderMap[folder] = pubFolder; | |
| 247 addContextToWorkQueue(pubFolder.context); | |
| 199 } | 248 } |
| 200 } | 249 } |
| 201 | 250 |
| 202 /** | 251 /** |
| 203 * Send the information in the given list of notices back to the client. | 252 * Return the [AnalysisContext] that is used to analyze the given [path]. |
| 253 * Return `null` if there is no such context. | |
| 204 */ | 254 */ |
| 205 void sendNotices(String contextId, List<ChangeNotice> notices) { | 255 AnalysisContext test_getAnalysisContext(String path) { |
|
Paul Berry
2014/05/27 16:30:29
What does it mean that this function (and test_get
scheglov
2014/05/27 18:24:24
These methods are supposed to be used only by test
| |
| 206 for (int i = 0; i < notices.length; i++) { | 256 for (Folder folder in folderMap.keys) { |
| 207 ChangeNotice notice = notices[i]; | 257 if (path.startsWith(folder.fullName)) { |
|
Brian Wilkerson
2014/05/27 14:09:57
Would it be better to use the names of folders as
scheglov
2014/05/27 18:24:24
I don't know.
In theory Resource can provide some
| |
| 208 Notification notification = new Notification(ERROR_NOTIFICATION_NAME); | 258 return folderMap[folder].context; |
| 209 notification.setParameter(CONTEXT_ID_PARAM, contextId); | 259 } |
| 210 notification.setParameter(SOURCE_PARAM, notice.source.encoding); | |
| 211 notification.setParameter(ERRORS_PARAM, notice.errors.map( | |
| 212 errorToJson).toList()); | |
| 213 sendNotification(notification); | |
| 214 } | 260 } |
| 261 return null; | |
| 262 } | |
| 263 | |
| 264 /** | |
| 265 * Return the [CompilationUnit] of the Dart file with the given [path]. | |
|
Paul Berry
2014/05/27 16:30:29
What happens if the dart file in question isn't un
scheglov
2014/05/27 18:24:24
I guess we will have to check if the file is under
| |
| 266 * Return `null` if the file is not a part of any context. | |
| 267 */ | |
| 268 CompilationUnit test_getResolvedCompilationUnit(String path) { | |
| 269 // prepare AnalysisContext | |
| 270 AnalysisContext context = test_getAnalysisContext(path); | |
| 271 if (context == null) { | |
| 272 return null; | |
| 273 } | |
| 274 // prepare sources | |
| 275 File file = resourceProvider.getResource(path); | |
| 276 Source unitSource = file.createSource(UriKind.FILE_URI); | |
| 277 List<Source> librarySources = context.getLibrariesContaining(unitSource); | |
| 278 if (librarySources.isEmpty) { | |
| 279 return null; | |
| 280 } | |
| 281 // get a resolved unit | |
| 282 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]); | |
| 215 } | 283 } |
| 216 | 284 |
| 217 static Map<String, Object> errorToJson(AnalysisError analysisError) { | 285 static Map<String, Object> errorToJson(AnalysisError analysisError) { |
| 218 // TODO(paulberry): move this function into the AnalysisError class. | 286 // TODO(paulberry): move this function into the AnalysisError class. |
| 219 | 287 |
| 220 // TODO(paulberry): we really shouldn't be exposing errorCode.ordinal | 288 // TODO(paulberry): we really shouldn't be exposing errorCode.ordinal |
| 221 // outside the analyzer, since the ordinal numbers change whenever we | 289 // outside the analyzer, since the ordinal numbers change whenever we |
| 222 // regenerate the analysis engine. | 290 // regenerate the analysis engine. |
| 223 Map<String, Object> result = { | 291 Map<String, Object> result = { |
| 224 'source': analysisError.source.encoding, | 292 '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); | 326 static const AnalysisService OUTLINE = const AnalysisService('OUTLINE', 3); |
| 259 | 327 |
| 260 static const List<AnalysisService> VALUES = | 328 static const List<AnalysisService> VALUES = |
| 261 const [ERRORS, HIGHLIGHTS, NAVIGATION, OUTLINE]; | 329 const [ERRORS, HIGHLIGHTS, NAVIGATION, OUTLINE]; |
| 262 | 330 |
| 263 const AnalysisService(String name, int ordinal) : super(name, ordinal); | 331 const AnalysisService(String name, int ordinal) : super(name, ordinal); |
| 264 } | 332 } |
| 265 | 333 |
| 266 | 334 |
| 267 /** | 335 /** |
| 336 * Instances of [PubFolder] represents a [Folder] with a Pub `pubspec.yaml`. | |
| 337 * | |
| 338 * TODO(scheglov) implement complete projects/contexts semantics. | |
| 339 * | |
| 340 * This class is intentionally simplified to serve as a base to start working | |
| 341 * on services while work on complete semantics is being done in parallel. | |
| 342 */ | |
| 343 class PubFolder { | |
| 344 static final DartSdk DEFAULT_SDK = DirectoryBasedDartSdk.defaultSdk; | |
|
Brian Wilkerson
2014/05/27 14:09:57
I don't think this is the best place to store the
scheglov
2014/05/27 18:24:24
Done.
| |
| 345 | |
| 346 /** | |
| 347 * The root [Folder] of this [PubFolder]. | |
| 348 */ | |
| 349 final Folder _folder; | |
| 350 | |
| 351 /** | |
| 352 * The `pubspec.yaml` file in [_folder]. | |
| 353 */ | |
| 354 File _pubspecFile; | |
| 355 | |
| 356 /** | |
| 357 * The [AnalysisContext] of this [_folder]. | |
| 358 */ | |
| 359 AnalysisContext _context; | |
| 360 | |
| 361 PubFolder(this._folder) { | |
| 362 // prepare pubspec.yaml | |
| 363 _pubspecFile = _folder.getChild('pubspec.yaml'); | |
| 364 if (!_pubspecFile.exists) { | |
| 365 throw new ArgumentError('$_pubspecFile does not exist'); | |
| 366 } | |
| 367 // TODO(scheglov) use configurable default SDK | |
| 368 DartSdk sdk = DEFAULT_SDK; | |
| 369 // create AnalysisContext | |
| 370 _context = AnalysisEngine.instance.createAnalysisContext(); | |
| 371 // TODO(scheglov) replace FileUriResolver with an Resource based resolver | |
| 372 // TODO(scheglov) create packages resolver | |
| 373 _context.sourceFactory = new SourceFactory([ | |
| 374 new DartUriResolver(sdk), | |
| 375 new FileUriResolver(), | |
| 376 // new PackageUriResolver(), | |
| 377 ]); | |
| 378 // add folder files | |
| 379 { | |
| 380 ChangeSet changeSet = new ChangeSet(); | |
| 381 _addSourceFiles(changeSet, _folder); | |
| 382 _context.applyChanges(changeSet); | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 /** | |
| 387 * Return the [AnalysisContext] of this folder. | |
| 388 */ | |
| 389 AnalysisContext get context => _context; | |
| 390 | |
| 391 /** | |
| 392 * Resursively adds all Dart and HTML files to the [changeSet]. | |
| 393 */ | |
| 394 static void _addSourceFiles(ChangeSet changeSet, Folder folder) { | |
| 395 List<Resource> children = folder.getChildren(); | |
| 396 for (Resource child in children) { | |
| 397 if (child is File) { | |
| 398 String fileName = child.shortName; | |
| 399 if (AnalysisEngine.isDartFileName(fileName) | |
| 400 || AnalysisEngine.isHtmlFileName(fileName)) { | |
| 401 Source source = child.createSource(UriKind.FILE_URI); | |
| 402 changeSet.addedSource(source); | |
| 403 } | |
| 404 } else if (child is Folder) { | |
| 405 _addSourceFiles(changeSet, child); | |
| 406 } | |
| 407 } | |
| 408 } | |
| 409 } | |
| 410 | |
| 411 | |
| 412 /** | |
| 268 * An enumeration of the services provided by the server domain. | 413 * An enumeration of the services provided by the server domain. |
| 269 */ | 414 */ |
| 270 class ServerService extends Enum2<ServerService> { | 415 class ServerService extends Enum2<ServerService> { |
| 271 static const ServerService STATUS = const ServerService('STATUS', 0); | 416 static const ServerService STATUS = const ServerService('STATUS', 0); |
| 272 | 417 |
| 273 static const List<ServerService> VALUES = const [STATUS]; | 418 static const List<ServerService> VALUES = const [STATUS]; |
| 274 | 419 |
| 275 const ServerService(String name, int ordinal) : super(name, ordinal); | 420 const ServerService(String name, int ordinal) : super(name, ordinal); |
| 276 } | 421 } |
| OLD | NEW |