Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: observatory_pub_packages/code_transformers/src/resolver_impl.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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/analyzer.dart' show parseDirectives;
9 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator;
10 import 'package:analyzer/src/generated/constant.dart' show ConstantEvaluator,
11 EvaluationResult;
12 import 'package:analyzer/src/generated/element.dart';
13 import 'package:analyzer/src/generated/engine.dart';
14 import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
15 import 'package:analyzer/src/generated/source.dart';
16 import 'package:barback/barback.dart';
17 import 'package:code_transformers/assets.dart';
18 import 'package:path/path.dart' as native_path;
19 import 'package:source_maps/refactor.dart';
20 import 'package:source_span/source_span.dart';
21
22 import 'resolver.dart';
23 import 'dart_sdk.dart' show UriAnnotatedSource;
24
25 // We should always be using url paths here since it's always Dart/pub code.
26 final path = native_path.url;
27
28 /// Resolves and updates an AST based on Barback-based assets.
29 ///
30 /// This also provides a handful of useful APIs for traversing and working
31 /// with the resolved AST.
32 class ResolverImpl implements Resolver {
33 /// Cache of all asset sources currently referenced.
34 final Map<AssetId, _AssetBasedSource> sources =
35 <AssetId, _AssetBasedSource>{};
36
37 final InternalAnalysisContext _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 entry libraries, or null if nothing is resolved.
44 List<LibraryElement> _entryLibraries;
45 Set<LibraryElement> _libraries;
46
47 /// Future indicating when this resolver is done in the current phase.
48 Future _lastPhaseComplete = new Future.value();
49
50 /// Completer for wrapping up the current phase.
51 Completer _currentPhaseComplete;
52
53 /// Creates a resolver with a given [sdk] implementation for resolving
54 /// `dart:*` imports.
55 ResolverImpl(DartSdk sdk, DartUriResolver dartUriResolver,
56 {AnalysisOptions options}) {
57 if (options == null) {
58 options = new AnalysisOptionsImpl()
59 ..cacheSize = 256 // # of sources to cache ASTs for.
60 ..preserveComments = false
61 ..analyzeFunctionBodies = true;
62 }
63 _context.analysisOptions = options;
64 sdk.context.analysisOptions = options;
65 _context.sourceFactory = new SourceFactory([dartUriResolver,
66 new _AssetUriResolver(this)]);
67 }
68
69 LibraryElement getLibrary(AssetId assetId) {
70 var source = sources[assetId];
71 return source == null ? null : _context.computeLibraryElement(source);
72 }
73
74 Future<Resolver> resolve(Transform transform, [List<AssetId> entryPoints]) {
75 // Can only have one resolve in progress at a time, so chain the current
76 // resolution to be after the last one.
77 var phaseComplete = new Completer();
78 var future = _lastPhaseComplete.whenComplete(() {
79 _currentPhaseComplete = phaseComplete;
80 return _performResolve(transform,
81 entryPoints == null ? [transform.primaryInput.id] : entryPoints);
82 }).then((_) => this);
83 // Advance the lastPhaseComplete to be done when this phase is all done.
84 _lastPhaseComplete = phaseComplete.future;
85 return future;
86 }
87
88 void release() {
89 if (_currentPhaseComplete == null) {
90 throw new StateError('Releasing without current lock.');
91 }
92 _currentPhaseComplete.complete(null);
93 _currentPhaseComplete = null;
94
95 // Clear out libraries since they should not be referenced after release.
96 _entryLibraries = null;
97 _libraries = null;
98 _currentTransform = null;
99 }
100
101 Future _performResolve(Transform transform, List<AssetId> entryPoints) {
102 if (_currentTransform != null) {
103 throw new StateError('Cannot be accessed by concurrent transforms');
104 }
105 _currentTransform = transform;
106
107 // Basic approach is to start at the first file, update it's contents
108 // and see if it changed, then walk all files accessed by it.
109 var visited = new Set<AssetId>();
110 var visiting = new FutureGroup();
111 var toUpdate = [];
112
113 void processAsset(AssetId assetId) {
114 visited.add(assetId);
115
116 visiting.add(transform.readInputAsString(assetId).then((contents) {
117 var source = sources[assetId];
118 if (source == null) {
119 source = new _AssetBasedSource(assetId, this);
120 sources[assetId] = source;
121 }
122 source.updateDependencies(contents);
123 toUpdate.add(new _PendingUpdate(source, contents));
124 source.dependentAssets.where((id) => !visited.contains(id))
125 .forEach(processAsset);
126 }, onError: (e) {
127 var source = sources[assetId];
128 if (source != null && source.exists()) {
129 _context.applyChanges(
130 new ChangeSet()..removedSource(source));
131 sources[assetId].updateContents(null);
132 }
133 }));
134 }
135 entryPoints.forEach(processAsset);
136
137 // Once we have all asset sources updated with the new contents then
138 // resolve everything.
139 return visiting.future.then((_) {
140 var changeSet = new ChangeSet();
141 toUpdate.forEach((pending) => pending.apply(changeSet));
142 var unreachableAssets = sources.keys.toSet()
143 .difference(visited)
144 .map((id) => sources[id]);
145 for (var unreachable in unreachableAssets) {
146 changeSet.removedSource(unreachable);
147 unreachable.updateContents(null);
148 sources.remove(unreachable.assetId);
149 }
150
151 // Update the analyzer context with the latest sources
152 _context.applyChanges(changeSet);
153 // Force resolve each entry point (the getter will ensure the library is
154 // computed first).
155 _entryLibraries = entryPoints.map((id) {
156 var source = sources[id];
157 if (source == null) return null;
158 return _context.computeLibraryElement(source);
159 }).toList();
160 });
161 }
162
163 Iterable<LibraryElement> get libraries {
164 if (_libraries == null) {
165 // Note: we don't use `lib.visibleLibraries` because that excludes the
166 // exports seen in the entry libraries.
167 _libraries = new Set<LibraryElement>();
168 _entryLibraries.forEach(_collectLibraries);
169 }
170 return _libraries;
171 }
172
173 void _collectLibraries(LibraryElement lib) {
174 if (lib == null || _libraries.contains(lib)) return;
175 _libraries.add(lib);
176 lib.importedLibraries.forEach(_collectLibraries);
177 lib.exportedLibraries.forEach(_collectLibraries);
178 }
179
180 LibraryElement getLibraryByName(String libraryName) =>
181 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null);
182
183 LibraryElement getLibraryByUri(Uri uri) =>
184 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null);
185
186 ClassElement getType(String typeName) {
187 var dotIndex = typeName.lastIndexOf('.');
188 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex);
189
190 var className = dotIndex == -1 ?
191 typeName : typeName.substring(dotIndex + 1);
192
193 for (var lib in libraries.where((l) => l.name == libraryName)) {
194 var type = lib.getType(className);
195 if (type != null) return type;
196 }
197 return null;
198 }
199
200 Element getLibraryVariable(String variableName) {
201 var dotIndex = variableName.lastIndexOf('.');
202 var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex);
203
204 var name = dotIndex == -1 ?
205 variableName : variableName.substring(dotIndex + 1);
206
207 return libraries.where((lib) => lib.name == libraryName)
208 .expand((lib) => lib.units)
209 .expand((unit) => unit.topLevelVariables)
210 .firstWhere((variable) => variable.name == name,
211 orElse: () => null);
212 }
213
214 Element getLibraryFunction(String fnName) {
215 var dotIndex = fnName.lastIndexOf('.');
216 var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex);
217
218 var name = dotIndex == -1 ?
219 fnName : fnName.substring(dotIndex + 1);
220
221 return libraries.where((lib) => lib.name == libraryName)
222 .expand((lib) => lib.units)
223 .expand((unit) => unit.functions)
224 .firstWhere((fn) => fn.name == name,
225 orElse: () => null);
226 }
227
228 EvaluationResult evaluateConstant(
229 LibraryElement library, Expression expression) {
230 return new ConstantEvaluator(library.source, _context.typeProvider)
231 .evaluate(expression);
232 }
233
234 Uri getImportUri(LibraryElement lib, {AssetId from}) =>
235 _getSourceUri(lib, from: from);
236
237
238 /// Similar to getImportUri but will get the part URI for parts rather than
239 /// the library URI.
240 Uri _getSourceUri(Element element, {AssetId from}) {
241 var source = element.source;
242 if (source is _AssetBasedSource) {
243 return source.getSourceUri(from);
244 } else if (source is UriAnnotatedSource) {
245 return source.uri;
246 }
247 // Should not be able to encounter any other source types.
248 throw new StateError('Unable to resolve URI for ${source.runtimeType}');
249 }
250
251 AssetId getSourceAssetId(Element element) {
252 var source = element.source;
253 if (source is _AssetBasedSource) return source.assetId;
254 return null;
255 }
256
257 SourceSpan getSourceSpan(Element element) {
258 var sourceFile = getSourceFile(element);
259 if (sourceFile == null) return null;
260 return sourceFile.span(element.node.offset, element.node.end);
261 }
262
263 TextEditTransaction createTextEditTransaction(Element element) {
264 if (element.source is! _AssetBasedSource) return null;
265
266 // Cannot edit unless there is an active transformer.
267 if (_currentTransform == null) return null;
268
269 _AssetBasedSource source = element.source;
270 // Cannot modify assets in other packages.
271 if (source.assetId.package != _currentTransform.primaryInput.id.package) {
272 return null;
273 }
274
275 var sourceFile = getSourceFile(element);
276 if (sourceFile == null) return null;
277
278 return new TextEditTransaction(source.rawContents, sourceFile);
279 }
280
281 /// Gets the SourceFile for the source of the element.
282 SourceFile getSourceFile(Element element) {
283 var assetId = getSourceAssetId(element);
284 if (assetId == null) return null;
285
286 var importUri = _getSourceUri(element);
287 var spanPath = importUri != null ? importUri.toString() : assetId.path;
288 return new SourceFile(sources[assetId].rawContents, url: spanPath);
289 }
290 }
291
292 /// Implementation of Analyzer's Source for Barback based assets.
293 class _AssetBasedSource extends Source {
294
295 /// Asset ID where this source can be found.
296 final AssetId assetId;
297
298 /// The resolver this is being used in.
299 final ResolverImpl _resolver;
300
301 /// Cache of dependent asset IDs, to avoid re-parsing the AST.
302 Iterable<AssetId> _dependentAssets;
303
304 /// The current revision of the file, incremented only when file changes.
305 int _revision = 0;
306
307 /// The file contents.
308 String _contents;
309
310 _AssetBasedSource(this.assetId, this._resolver);
311
312 /// Update the dependencies of this source. This parses [contents] but avoids
313 /// any analyzer resolution.
314 void updateDependencies(String contents) {
315 if (contents == _contents) return;
316 var unit = parseDirectives(contents, suppressErrors: true);
317 _dependentAssets = unit.directives
318 .where((d) => (d is ImportDirective || d is PartDirective ||
319 d is ExportDirective))
320 .map((d) => _resolve(assetId, d.uri.stringValue, _logger,
321 _getSpan(d, contents)))
322 .where((id) => id != null).toSet();
323 }
324
325 /// Update the contents of this file with [contents].
326 ///
327 /// Returns true if the contents of this asset have changed.
328 bool updateContents(String contents) {
329 if (contents == _contents) return false;
330 _contents = contents;
331 ++_revision;
332 return true;
333 }
334
335 /// Contents of the file.
336 TimestampedData<String> get contents {
337 if (!exists()) throw new StateError('$assetId does not exist');
338
339 return new TimestampedData<String>(modificationStamp, _contents);
340 }
341
342 /// Contents of the file.
343 String get rawContents => _contents;
344
345 Uri get uri => Uri.parse('asset:${assetId.package}/${assetId.path}');
346
347 /// Logger for the current transform.
348 ///
349 /// Only valid while the resolver is updating assets.
350 TransformLogger get _logger => _resolver._currentTransform.logger;
351
352 /// Gets all imports/parts/exports which resolve to assets (non-Dart files).
353 Iterable<AssetId> get dependentAssets => _dependentAssets;
354
355 bool exists() => _contents != null;
356
357 bool operator ==(Object other) =>
358 other is _AssetBasedSource && assetId == other.assetId;
359
360 int get hashCode => assetId.hashCode;
361
362 void getContentsToReceiver(Source_ContentReceiver receiver) {
363 receiver.accept(rawContents, modificationStamp);
364 }
365
366 String get encoding =>
367 "${uriKind.encoding}${assetId.package}/${assetId.path}";
368
369 String get fullName => assetId.toString();
370
371 int get modificationStamp => _revision;
372
373 String get shortName => path.basename(assetId.path);
374
375 UriKind get uriKind {
376 if (assetId.path.startsWith('lib/')) return UriKind.PACKAGE_URI;
377 return UriKind.FILE_URI;
378 }
379
380 bool get isInSystemLibrary => false;
381
382 Source resolveRelative(Uri relativeUri) {
383 var id = _resolve(assetId, relativeUri.toString(), _logger, null);
384 if (id == null) return null;
385
386 // The entire AST should have been parsed and loaded at this point.
387 var source = _resolver.sources[id];
388 if (source == null) {
389 _logger.error('Could not load asset $id');
390 }
391 return source;
392 }
393
394 Uri resolveRelativeUri(Uri relativeUri) {
395 var id = _resolve(assetId, relativeUri.toString(), _logger, null);
396 if (id == null) return uri.resolveUri(relativeUri);
397
398 // The entire AST should have been parsed and loaded at this point.
399 var source = _resolver.sources[id];
400 if (source == null) {
401 _logger.error('Could not load asset $id');
402 }
403 return source.uri;
404 }
405
406 /// For logging errors.
407 SourceSpan _getSpan(AstNode node, [String contents]) =>
408 _getSourceFile(contents).span(node.offset, node.end);
409 /// For logging errors.
410 SourceFile _getSourceFile([String contents]) {
411 var uri = getSourceUri();
412 var path = uri != null ? uri.toString() : assetId.path;
413 return new SourceFile(contents != null ? contents : rawContents, url: path);
414 }
415
416 /// Gets a URI which would be appropriate for importing this file.
417 ///
418 /// Note that this file may represent a non-importable file such as a part.
419 Uri getSourceUri([AssetId from]) {
420 if (!assetId.path.startsWith('lib/')) {
421 // Cannot do absolute imports of non lib-based assets.
422 if (from == null) return null;
423
424 if (assetId.package != from.package) return null;
425 return new Uri(
426 path: path.relative(assetId.path, from: path.dirname(from.path)));
427 }
428
429 return Uri.parse('package:${assetId.package}/${assetId.path.substring(4)}');
430 }
431 }
432
433 /// Implementation of Analyzer's UriResolver for Barback based assets.
434 class _AssetUriResolver implements UriResolver {
435 final ResolverImpl _resolver;
436 _AssetUriResolver(this._resolver);
437
438 Source resolveAbsolute(Uri uri) {
439 assert(uri.scheme != 'dart');
440 var assetId;
441 if (uri.scheme == 'asset') {
442 var parts = path.split(uri.path);
443 assetId = new AssetId(parts[0], path.joinAll(parts.skip(1)));
444 } else {
445 assetId = _resolve(null, uri.toString(), logger, null);
446 if (assetId == null) {
447 logger.error('Unable to resolve asset ID for "$uri"');
448 return null;
449 }
450 }
451 var source = _resolver.sources[assetId];
452 // Analyzer expects that sources which are referenced but do not exist yet
453 // still exist, so just make an empty source.
454 if (source == null) {
455 source = new _AssetBasedSource(assetId, _resolver);
456 _resolver.sources[assetId] = source;
457 }
458 return source;
459 }
460
461 Source fromEncoding(UriKind kind, Uri uri) =>
462 throw new UnsupportedError('fromEncoding is not supported');
463
464 Uri restoreAbsolute(Source source) =>
465 throw new UnsupportedError('restoreAbsolute is not supported');
466
467 TransformLogger get logger => _resolver._currentTransform.logger;
468 }
469
470 /// Get an asset ID for a URL relative to another source asset.
471 AssetId _resolve(AssetId source, String url, TransformLogger logger,
472 SourceSpan span) {
473 if (url == null || url == '') return null;
474 var uri = Uri.parse(url);
475
476 // Workaround for dartbug.com/17156- pub transforms package: imports from
477 // files of the transformers package to have absolute /packages/ URIs.
478 if (uri.scheme == '' && path.isAbsolute(url)
479 && uri.pathSegments[0] == 'packages') {
480 uri = Uri.parse('package:${uri.pathSegments.skip(1).join(path.separator)}');
481 }
482
483 if (uri.scheme == 'package') {
484 var segments = new List.from(uri.pathSegments);
485 var package = segments[0];
486 segments[0] = 'lib';
487 return new AssetId(package, segments.join(path.separator));
488 }
489 // Dart SDK libraries do not have assets.
490 if (uri.scheme == 'dart') return null;
491
492 return uriToAssetId(source, url, logger, span);
493 }
494
495
496 /// A completer that waits until all added [Future]s complete.
497 // TODO(blois): Copied from quiver. Remove from here when it gets
498 // added to dart:core. (See #6626.)
499 class FutureGroup<E> {
500 static const _FINISHED = -1;
501
502 int _pending = 0;
503 Future _failedTask;
504 final Completer<List> _completer = new Completer<List>();
505 final List results = [];
506
507 /** Gets the task that failed, if any. */
508 Future get failedTask => _failedTask;
509
510 /**
511 * Wait for [task] to complete.
512 *
513 * If this group has already been marked as completed, a [StateError] will be
514 * thrown.
515 *
516 * If this group has a [failedTask], new tasks will be ignored, because the
517 * error has already been signaled.
518 */
519 void add(Future task) {
520 if (_failedTask != null) return;
521 if (_pending == _FINISHED) throw new StateError("Future already completed");
522
523 _pending++;
524 var i = results.length;
525 results.add(null);
526 task.then((res) {
527 results[i] = res;
528 if (_failedTask != null) return;
529 _pending--;
530 if (_pending == 0) {
531 _pending = _FINISHED;
532 _completer.complete(results);
533 }
534 }, onError: (e, s) {
535 if (_failedTask != null) return;
536 _failedTask = task;
537 _completer.completeError(e, s);
538 });
539 }
540
541 /**
542 * A Future that completes with a List of the values from all the added
543 * tasks, when they have all completed.
544 *
545 * If any task fails, this Future will receive the error. Only the first
546 * error will be sent to the Future.
547 */
548 Future<List<E>> get future => _completer.future;
549 }
550
551 /// A pending update to notify the resolver that a [Source] has been added or
552 /// changed. This is used by the `_performResolve` algorithm above to apply all
553 /// changes after it first discovers the transitive closure of files that are
554 /// reachable from the sources.
555 class _PendingUpdate {
556 _AssetBasedSource source;
557 String content;
558
559 _PendingUpdate(this.source, this.content);
560
561 void apply(ChangeSet changeSet) {
562 if (!source.updateContents(content)) return;
563 if (source._revision == 1 && source._contents != null) {
564 changeSet.addedSource(source);
565 } else {
566 changeSet.changedSource(source);
567 }
568 }
569 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698