OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library code_transformer.src.resolver_impl; | |
6 | |
7 import 'dart:async'; | |
8 import 'package:analyzer/src/generated/ast.dart'; | |
9 import 'package:analyzer/src/generated/element.dart'; | |
10 import 'package:analyzer/src/generated/engine.dart'; | |
11 import 'package:analyzer/src/generated/error.dart'; | |
12 import 'package:analyzer/src/generated/java_io.dart'; | |
13 import 'package:analyzer/src/generated/parser.dart' show Parser; | |
14 import 'package:analyzer/src/generated/scanner.dart'; | |
15 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; | |
16 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; | |
17 import 'package:analyzer/src/generated/source.dart'; | |
18 import 'package:barback/barback.dart'; | |
19 import 'package:path/path.dart' as path; | |
20 import 'package:source_maps/refactor.dart'; | |
21 import 'package:source_maps/span.dart' show SourceFile, Span; | |
22 | |
23 import 'resolver.dart'; | |
24 | |
25 /// Resolves and updates an AST based on Barback-based assets. | |
26 /// | |
27 /// This also provides a handful of useful APIs for traversing and working | |
28 /// with the resolved AST. | |
29 class ResolverImpl implements Resolver { | |
30 /// Cache of all asset sources currently referenced. | |
31 final Map<AssetId, _AssetBasedSource> sources = | |
32 <AssetId, _AssetBasedSource>{}; | |
33 | |
34 /// The Dart entry point file where parsing begins. | |
35 final AssetId entryPoint; | |
36 | |
37 final AnalysisContext _context = | |
38 AnalysisEngine.instance.createAnalysisContext(); | |
39 | |
40 /// Transform for which this is currently updating, or null when not updating. | |
41 Transform _currentTransform; | |
42 | |
43 /// The currently resolved library, or null if unresolved. | |
44 LibraryElement _entryLibrary; | |
45 | |
46 /// Handler for all Dart SDK (dart:) sources. | |
47 DirectoryBasedDartSdk _dartSdk; | |
48 | |
49 /// Creates a resolver that will resolve the Dart code starting at | |
50 /// [entryPoint]. | |
51 /// | |
52 /// [sdkDir] is the root directory of the Dart SDK, for resolving dart: | |
53 /// imports. | |
54 ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) { | |
55 if (options == null) { | |
56 options = new AnalysisOptionsImpl() | |
57 ..cacheSize = 256 // # of sources to cache ASTs for. | |
58 ..preserveComments = false | |
59 ..analyzeFunctionBodies = true; | |
60 } | |
61 _context.analysisOptions = options; | |
62 | |
63 _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir)); | |
64 _dartSdk.context.analysisOptions = options; | |
65 | |
66 _context.sourceFactory = new SourceFactory.con2([ | |
67 new DartUriResolverProxy(_dartSdk), | |
68 new _AssetUriResolver(this)]); | |
69 } | |
70 | |
71 LibraryElement get entryLibrary => _entryLibrary; | |
72 | |
73 | |
74 /// Update the status of all the sources referenced by the entryPoint and | |
75 /// update the resolved library. | |
76 /// | |
77 /// This will be invoked automatically by [ResolverTransformer]. Only one | |
78 /// transformer may update this at a time. | |
79 Future updateSources(Transform transform) { | |
80 if (_currentTransform != null) { | |
81 throw new StateError('Cannot be accessed by concurrent transforms'); | |
82 } | |
83 _currentTransform = transform; | |
84 // Clear this out and update once all asset changes have been processed. | |
85 _entryLibrary = null; | |
86 | |
87 // Basic approach is to start at the first file, update it's contents | |
88 // and see if it changed, then walk all files accessed by it. | |
89 var visited = new Set<AssetId>(); | |
90 var visiting = new FutureGroup(); | |
91 | |
92 void processAsset(AssetId assetId) { | |
93 visited.add(assetId); | |
94 | |
95 visiting.add(transform.readInputAsString(assetId).then((contents) { | |
96 var source = sources[assetId]; | |
97 if (source == null) { | |
98 source = new _AssetBasedSource(assetId, this); | |
99 sources[assetId] = source; | |
100 } | |
101 source.updateContents(contents); | |
102 | |
103 source.dependentAssets | |
104 .where((id) => !visited.contains(id)) | |
105 .forEach(processAsset); | |
106 | |
107 }, onError: (e) { | |
108 _context.applyChanges(new ChangeSet()..removed(sources[assetId])); | |
109 sources.remove(assetId); | |
110 })); | |
111 } | |
112 processAsset(entryPoint); | |
113 | |
114 // Once we have all asset sources updated with the new contents then | |
115 // resolve everything. | |
116 return visiting.future.then((_) { | |
117 var changeSet = new ChangeSet(); | |
118 var unreachableAssets = new Set.from(sources.keys).difference(visited); | |
119 for (var unreachable in unreachableAssets) { | |
120 changeSet.removed(sources[unreachable]); | |
121 sources.remove(unreachable); | |
122 } | |
123 | |
124 // Update the analyzer context with the latest sources | |
125 _context.applyChanges(changeSet); | |
126 // Resolve the AST | |
127 _entryLibrary = _context.computeLibraryElement(sources[entryPoint]); | |
128 _currentTransform = null; | |
129 }); | |
130 } | |
131 | |
132 Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries; | |
133 | |
134 LibraryElement getLibraryByName(String libraryName) => | |
135 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null); | |
136 | |
137 LibraryElement getLibraryByUri(Uri uri) => | |
138 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null); | |
139 | |
140 ClassElement getType(String typeName) { | |
141 var dotIndex = typeName.lastIndexOf('.'); | |
142 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex); | |
143 | |
144 var className = dotIndex == -1 ? | |
145 typeName : typeName.substring(dotIndex + 1); | |
146 | |
147 for (var lib in libraries.where((l) => l.name == libraryName)) { | |
148 var type = lib.getType(className); | |
149 if (type != null) return type; | |
150 } | |
151 return null; | |
152 } | |
153 | |
154 Element getLibraryVariable(String variableName) { | |
155 var dotIndex = variableName.lastIndexOf('.'); | |
156 var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex); | |
157 | |
158 var name = dotIndex == -1 ? | |
159 variableName : variableName.substring(dotIndex + 1); | |
160 | |
161 return libraries.where((lib) => lib.name == libraryName) | |
162 .expand((lib) => lib.units) | |
163 .expand((unit) => unit.topLevelVariables) | |
164 .firstWhere((variable) => variable.name == name, | |
165 orElse: () => null); | |
166 } | |
167 | |
168 Element getLibraryFunction(String fnName) { | |
169 var dotIndex = fnName.lastIndexOf('.'); | |
170 var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex); | |
171 | |
172 var name = dotIndex == -1 ? | |
173 fnName : fnName.substring(dotIndex + 1); | |
174 | |
175 return libraries.where((lib) => lib.name == libraryName) | |
176 .expand((lib) => lib.units) | |
177 .expand((unit) => unit.functions) | |
178 .firstWhere((fn) => fn.name == name, | |
179 orElse: () => null); | |
180 } | |
181 | |
182 Uri getImportUri(LibraryElement lib, {AssetId from}) => | |
183 _getSourceUri(lib, from: from); | |
184 | |
185 | |
186 /// Similar to getImportUri but will get the part URI for parts rather than | |
187 /// the library URI. | |
188 Uri _getSourceUri(Element element, {AssetId from}) { | |
189 var source = element.source; | |
190 if (source is _AssetBasedSource) { | |
191 return source.getSourceUri(from); | |
192 } else if (source is _DartSourceProxy) { | |
193 return source.uri; | |
194 } | |
195 // Should not be able to encounter any other source types. | |
196 throw new StateError('Unable to resolve URI for ${source.runtimeType}'); | |
197 } | |
198 | |
199 AssetId getSourceAssetId(Element element) { | |
200 var source = element.source; | |
201 if (source is _AssetBasedSource) return source.assetId; | |
202 return null; | |
203 } | |
204 | |
205 Span getSourceSpan(Element element) { | |
206 var sourceFile = _getSourceFile(element); | |
207 if (sourceFile == null) return null; | |
208 return sourceFile.span(element.node.offset, element.node.end); | |
209 } | |
210 | |
211 TextEditTransaction createTextEditTransaction(Element element) { | |
212 if (element.source is! _AssetBasedSource) return null; | |
213 | |
214 _AssetBasedSource source = element.source; | |
215 // Cannot modify assets in other packages. | |
216 if (source.assetId.package != entryPoint.package) return null; | |
217 | |
218 var sourceFile = _getSourceFile(element); | |
219 if (sourceFile == null) return null; | |
220 | |
221 return new TextEditTransaction(source.contents, sourceFile); | |
222 } | |
223 | |
224 /// Gets the SourceFile for the source of the element. | |
225 SourceFile _getSourceFile(Element element) { | |
226 var assetId = getSourceAssetId(element); | |
227 if (assetId == null) return null; | |
228 | |
229 var importUri = _getSourceUri(element, from: entryPoint); | |
230 var spanPath = importUri != null ? importUri.toString() : assetId.path; | |
231 return new SourceFile.text(spanPath, sources[assetId].contents); | |
232 } | |
233 } | |
234 | |
235 /// Implementation of Analyzer's Source for Barback based assets. | |
236 class _AssetBasedSource extends Source { | |
237 | |
238 /// Asset ID where this source can be found. | |
239 final AssetId assetId; | |
240 | |
241 /// The resolver this is being used in. | |
242 final ResolverImpl _resolver; | |
243 | |
244 /// Cache of dependent asset IDs, to avoid re-parsing the AST. | |
245 Iterable<AssetId> _dependentAssets; | |
246 | |
247 /// The current revision of the file, incremented only when file changes. | |
248 int _revision = 0; | |
249 | |
250 /// The file contents. | |
251 String _contents; | |
252 | |
253 _AssetBasedSource(this.assetId, this._resolver); | |
254 | |
255 /// Update the contents of this file with [contents]. | |
256 /// | |
257 /// Returns true if the contents of this asset have changed. | |
258 bool updateContents(String contents) { | |
259 if (contents == _contents) return false; | |
260 var added = _contents == null; | |
261 _contents = contents; | |
262 ++_revision; | |
263 // Invalidate the imports so we only parse the AST when needed. | |
264 _dependentAssets = null; | |
265 | |
266 if (added) { | |
267 _resolver._context.applyChanges(new ChangeSet()..added(this)); | |
268 } else { | |
269 _resolver._context.applyChanges(new ChangeSet()..changed(this)); | |
270 } | |
271 | |
272 var compilationUnit = _resolver._context.parseCompilationUnit(this); | |
273 _dependentAssets = compilationUnit.directives | |
274 .where((d) => (d is ImportDirective || d is PartDirective || | |
275 d is ExportDirective)) | |
276 .map((d) => _resolve(assetId, d.uri.stringValue, | |
277 _logger, _getSpan(d))) | |
278 .where((id) => id != null).toSet(); | |
279 return true; | |
280 } | |
281 | |
282 /// Contents of the file. | |
283 String get contents => _contents; | |
284 | |
285 /// Logger for the current transform. | |
286 /// | |
287 /// Only valid while the resolver is updating assets. | |
288 TransformLogger get _logger => _resolver._currentTransform.logger; | |
289 | |
290 /// Gets all imports/parts/exports which resolve to assets (non-Dart files). | |
291 Iterable<AssetId> get dependentAssets => _dependentAssets; | |
292 | |
293 bool exists() => true; | |
294 | |
295 bool operator ==(Object other) => | |
296 other is _AssetBasedSource && assetId == other.assetId; | |
297 | |
298 int get hashCode => assetId.hashCode; | |
299 | |
300 void getContents(Source_ContentReceiver receiver) { | |
301 receiver.accept(contents, modificationStamp); | |
302 } | |
303 | |
304 String get encoding => | |
305 "${uriKind.encoding}${assetId.package}/${assetId.path}"; | |
306 | |
307 String get fullName => assetId.toString(); | |
308 | |
309 int get modificationStamp => _revision; | |
310 | |
311 String get shortName => path.basename(assetId.path); | |
312 | |
313 UriKind get uriKind { | |
314 if (assetId.path.startsWith('lib/')) return UriKind.PACKAGE_URI; | |
315 return UriKind.FILE_URI; | |
316 } | |
317 | |
318 bool get isInSystemLibrary => false; | |
319 | |
320 Source resolveRelative(Uri relativeUri) { | |
321 var id = _resolve(assetId, relativeUri.toString(), _logger, null); | |
322 if (id == null) return null; | |
323 | |
324 // The entire AST should have been parsed and loaded at this point. | |
325 var source = _resolver.sources[id]; | |
326 if (source == null) { | |
327 _logger.error('Could not load asset $id'); | |
328 } | |
329 return source; | |
330 } | |
331 | |
332 /// For logging errors. | |
333 Span _getSpan(ASTNode node) => _sourceFile.span(node.offset, node.end); | |
334 /// For logging errors. | |
335 SourceFile get _sourceFile { | |
336 var uri = getSourceUri(_resolver.entryPoint); | |
337 var path = uri != null ? uri.toString() : assetId.path; | |
338 | |
339 return new SourceFile.text(path, contents); | |
340 } | |
341 | |
342 /// Gets a URI which would be appropriate for importing this file. | |
343 /// | |
344 /// Note that this file may represent a non-importable file such as a part. | |
345 Uri getSourceUri([AssetId from]) { | |
346 if (!assetId.path.startsWith('lib/')) { | |
347 // Cannot do absolute imports of non lib-based assets. | |
348 if (from == null) return null; | |
349 | |
350 if (assetId.package != from.package) return null; | |
351 return new Uri( | |
352 path: path.relative(assetId.path, from: path.dirname(from.path))); | |
353 } | |
354 | |
355 return Uri.parse('package:${assetId.package}/${assetId.path.substring(4)}'); | |
356 } | |
357 } | |
358 | |
359 /// Implementation of Analyzer's UriResolver for Barback based assets. | |
360 class _AssetUriResolver implements UriResolver { | |
361 final ResolverImpl _resolver; | |
362 _AssetUriResolver(this._resolver); | |
363 | |
364 Source resolveAbsolute(ContentCache contentCache, Uri uri) { | |
365 var assetId = _resolve(null, uri.toString(), logger, null); | |
366 var source = _resolver.sources[assetId]; | |
367 /// All resolved assets should be available by this point. | |
368 if (source == null) { | |
369 logger.error('Unable to find asset for "$uri"'); | |
370 } | |
371 return source; | |
372 } | |
373 | |
374 Source fromEncoding(ContentCache contentCache, UriKind kind, Uri uri) => | |
375 throw new UnsupportedError('fromEncoding is not supported'); | |
376 | |
377 Uri restoreAbsolute(Source source) => | |
378 throw new UnsupportedError('restoreAbsolute is not supported'); | |
379 | |
380 TransformLogger get logger => _resolver._currentTransform.logger; | |
381 } | |
382 | |
383 | |
384 /// Dart SDK which wraps all Dart sources to ensure they are tracked with Uris. | |
385 /// | |
386 /// Just a simple wrapper to make it easy to make sure that all sources we | |
387 /// encounter are either _AssetBasedSource or _DartSourceProxy. | |
388 class _DirectoryBasedDartSdkProxy extends DirectoryBasedDartSdk { | |
389 _DirectoryBasedDartSdkProxy(JavaFile sdkDirectory) : super(sdkDirectory); | |
390 | |
391 Source mapDartUri(String dartUri) => | |
392 _DartSourceProxy.wrap(super.mapDartUri(dartUri), Uri.parse(dartUri)); | |
393 } | |
394 | |
395 | |
396 /// Dart SDK resolver which wraps all Dart sources to ensure they are tracked | |
397 /// with URIs. | |
398 class DartUriResolverProxy implements DartUriResolver { | |
399 final DartUriResolver _proxy; | |
400 DartUriResolverProxy(DirectoryBasedDartSdk sdk) : | |
401 _proxy = new DartUriResolver(sdk); | |
402 | |
403 Source resolveAbsolute(ContentCache contentCache, Uri uri) => | |
404 _DartSourceProxy.wrap(_proxy.resolveAbsolute(contentCache, uri), uri); | |
405 | |
406 DartSdk get dartSdk => _proxy.dartSdk; | |
407 | |
408 Source fromEncoding(ContentCache contentCache, UriKind kind, Uri uri) => | |
409 throw new UnsupportedError('fromEncoding is not supported'); | |
410 | |
411 Uri restoreAbsolute(Source source) => | |
412 throw new UnsupportedError('restoreAbsolute is not supported'); | |
413 } | |
414 | |
415 /// Source file for dart: sources which track the sources with dart: URIs. | |
416 /// | |
417 /// This is primarily to support [Resolver.getImportUri] for Dart SDK (dart:) | |
418 /// based libraries. | |
419 class _DartSourceProxy implements Source { | |
420 | |
421 /// Absolute URI which this source can be imported from | |
422 final Uri uri; | |
423 | |
424 /// Underlying source object. | |
425 final Source _proxy; | |
426 | |
427 _DartSourceProxy(this._proxy, this.uri); | |
428 | |
429 /// Ensures that [source] is a _DartSourceProxy. | |
430 static _DartSourceProxy wrap(Source source, Uri uri) { | |
431 if (source == null || source is _DartSourceProxy) return source; | |
432 return new _DartSourceProxy(source, uri); | |
433 } | |
434 | |
435 Source resolveRelative(Uri relativeUri) { | |
436 // Assume that the type can be accessed via this URI, since these | |
437 // should only be parts for dart core files. | |
438 return wrap(_proxy.resolveRelative(relativeUri), uri); | |
439 } | |
440 | |
441 bool exists() => _proxy.exists(); | |
442 | |
443 bool operator ==(Object other) => | |
444 (other is _DartSourceProxy && _proxy == other._proxy); | |
445 | |
446 int get hashCode => _proxy.hashCode; | |
447 | |
448 void getContents(Source_ContentReceiver receiver) { | |
449 _proxy.getContents(receiver); | |
450 } | |
451 | |
452 String get encoding => _proxy.encoding; | |
453 | |
454 String get fullName => _proxy.fullName; | |
455 | |
456 int get modificationStamp => _proxy.modificationStamp; | |
457 | |
458 String get shortName => _proxy.shortName; | |
459 | |
460 UriKind get uriKind => _proxy.uriKind; | |
461 | |
462 bool get isInSystemLibrary => _proxy.isInSystemLibrary; | |
463 } | |
464 | |
465 /// Get an asset ID for a URL relative to another source asset. | |
466 AssetId _resolve(AssetId source, String url, TransformLogger logger, | |
467 Span span) { | |
468 if (url == null || url == '') return null; | |
469 var urlBuilder = path.url; | |
470 var uri = Uri.parse(url); | |
471 | |
472 if (uri.scheme == 'package') { | |
473 var segments = new List.from(uri.pathSegments); | |
474 var package = segments[0]; | |
475 segments[0] = 'lib'; | |
476 return new AssetId(package, segments.join(urlBuilder.separator)); | |
477 } | |
478 // Dart SDK libraries do not have assets. | |
479 if (uri.scheme == 'dart') return null; | |
480 | |
481 if (uri.host != '' || uri.scheme != '' || urlBuilder.isAbsolute(url)) { | |
482 logger.error('absolute paths not allowed: "$url"', span: span); | |
483 return null; | |
484 } | |
485 | |
486 var targetPath = urlBuilder.normalize( | |
487 urlBuilder.join(urlBuilder.dirname(source.path), url)); | |
488 return new AssetId(source.package, targetPath); | |
489 } | |
490 | |
491 | |
492 /// A completer that waits until all added [Future]s complete. | |
493 // TODO(blois): Copied from quiver. Remove from here when it gets | |
494 // added to dart:core. (See #6626.) | |
495 class FutureGroup<E> { | |
496 static const _FINISHED = -1; | |
497 | |
498 int _pending = 0; | |
499 Future _failedTask; | |
500 final Completer<List> _completer = new Completer<List>(); | |
501 final List results = []; | |
502 | |
503 /** Gets the task that failed, if any. */ | |
504 Future get failedTask => _failedTask; | |
505 | |
506 /** | |
507 * Wait for [task] to complete. | |
508 * | |
509 * If this group has already been marked as completed, a [StateError] will be | |
510 * thrown. | |
511 * | |
512 * If this group has a [failedTask], new tasks will be ignored, because the | |
513 * error has already been signaled. | |
514 */ | |
515 void add(Future task) { | |
516 if (_failedTask != null) return; | |
517 if (_pending == _FINISHED) throw new StateError("Future already completed"); | |
518 | |
519 _pending++; | |
520 var i = results.length; | |
521 results.add(null); | |
522 task.then((res) { | |
523 results[i] = res; | |
524 if (_failedTask != null) return; | |
525 _pending--; | |
526 if (_pending == 0) { | |
527 _pending = _FINISHED; | |
528 _completer.complete(results); | |
529 } | |
530 }, onError: (e, s) { | |
531 if (_failedTask != null) return; | |
532 _failedTask = task; | |
533 _completer.completeError(e, s); | |
534 }); | |
535 } | |
536 | |
537 /** | |
538 * A Future that complets with a List of the values from all the added | |
539 * tasks, when they have all completed. | |
540 * | |
541 * If any task fails, this Future will receive the error. Only the first | |
542 * error will be sent to the Future. | |
543 */ | |
544 Future<List<E>> get future => _completer.future; | |
545 } | |
OLD | NEW |