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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/code_transformers/src/resolver_impl.dart
===================================================================
--- observatory_pub_packages/code_transformers/src/resolver_impl.dart (revision 0)
+++ observatory_pub_packages/code_transformers/src/resolver_impl.dart (working copy)
@@ -0,0 +1,569 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformer.src.resolver_impl;
+
+import 'dart:async';
+import 'package:analyzer/analyzer.dart' show parseDirectives;
+import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator;
+import 'package:analyzer/src/generated/constant.dart' show ConstantEvaluator,
+ EvaluationResult;
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:barback/barback.dart';
+import 'package:code_transformers/assets.dart';
+import 'package:path/path.dart' as native_path;
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+
+import 'resolver.dart';
+import 'dart_sdk.dart' show UriAnnotatedSource;
+
+// We should always be using url paths here since it's always Dart/pub code.
+final path = native_path.url;
+
+/// Resolves and updates an AST based on Barback-based assets.
+///
+/// This also provides a handful of useful APIs for traversing and working
+/// with the resolved AST.
+class ResolverImpl implements Resolver {
+ /// Cache of all asset sources currently referenced.
+ final Map<AssetId, _AssetBasedSource> sources =
+ <AssetId, _AssetBasedSource>{};
+
+ final InternalAnalysisContext _context =
+ AnalysisEngine.instance.createAnalysisContext();
+
+ /// Transform for which this is currently updating, or null when not updating.
+ Transform _currentTransform;
+
+ /// The currently resolved entry libraries, or null if nothing is resolved.
+ List<LibraryElement> _entryLibraries;
+ Set<LibraryElement> _libraries;
+
+ /// Future indicating when this resolver is done in the current phase.
+ Future _lastPhaseComplete = new Future.value();
+
+ /// Completer for wrapping up the current phase.
+ Completer _currentPhaseComplete;
+
+ /// Creates a resolver with a given [sdk] implementation for resolving
+ /// `dart:*` imports.
+ ResolverImpl(DartSdk sdk, DartUriResolver dartUriResolver,
+ {AnalysisOptions options}) {
+ if (options == null) {
+ options = new AnalysisOptionsImpl()
+ ..cacheSize = 256 // # of sources to cache ASTs for.
+ ..preserveComments = false
+ ..analyzeFunctionBodies = true;
+ }
+ _context.analysisOptions = options;
+ sdk.context.analysisOptions = options;
+ _context.sourceFactory = new SourceFactory([dartUriResolver,
+ new _AssetUriResolver(this)]);
+ }
+
+ LibraryElement getLibrary(AssetId assetId) {
+ var source = sources[assetId];
+ return source == null ? null : _context.computeLibraryElement(source);
+ }
+
+ Future<Resolver> resolve(Transform transform, [List<AssetId> entryPoints]) {
+ // Can only have one resolve in progress at a time, so chain the current
+ // resolution to be after the last one.
+ var phaseComplete = new Completer();
+ var future = _lastPhaseComplete.whenComplete(() {
+ _currentPhaseComplete = phaseComplete;
+ return _performResolve(transform,
+ entryPoints == null ? [transform.primaryInput.id] : entryPoints);
+ }).then((_) => this);
+ // Advance the lastPhaseComplete to be done when this phase is all done.
+ _lastPhaseComplete = phaseComplete.future;
+ return future;
+ }
+
+ void release() {
+ if (_currentPhaseComplete == null) {
+ throw new StateError('Releasing without current lock.');
+ }
+ _currentPhaseComplete.complete(null);
+ _currentPhaseComplete = null;
+
+ // Clear out libraries since they should not be referenced after release.
+ _entryLibraries = null;
+ _libraries = null;
+ _currentTransform = null;
+ }
+
+ Future _performResolve(Transform transform, List<AssetId> entryPoints) {
+ if (_currentTransform != null) {
+ throw new StateError('Cannot be accessed by concurrent transforms');
+ }
+ _currentTransform = transform;
+
+ // Basic approach is to start at the first file, update it's contents
+ // and see if it changed, then walk all files accessed by it.
+ var visited = new Set<AssetId>();
+ var visiting = new FutureGroup();
+ var toUpdate = [];
+
+ void processAsset(AssetId assetId) {
+ visited.add(assetId);
+
+ visiting.add(transform.readInputAsString(assetId).then((contents) {
+ var source = sources[assetId];
+ if (source == null) {
+ source = new _AssetBasedSource(assetId, this);
+ sources[assetId] = source;
+ }
+ source.updateDependencies(contents);
+ toUpdate.add(new _PendingUpdate(source, contents));
+ source.dependentAssets.where((id) => !visited.contains(id))
+ .forEach(processAsset);
+ }, onError: (e) {
+ var source = sources[assetId];
+ if (source != null && source.exists()) {
+ _context.applyChanges(
+ new ChangeSet()..removedSource(source));
+ sources[assetId].updateContents(null);
+ }
+ }));
+ }
+ entryPoints.forEach(processAsset);
+
+ // Once we have all asset sources updated with the new contents then
+ // resolve everything.
+ return visiting.future.then((_) {
+ var changeSet = new ChangeSet();
+ toUpdate.forEach((pending) => pending.apply(changeSet));
+ var unreachableAssets = sources.keys.toSet()
+ .difference(visited)
+ .map((id) => sources[id]);
+ for (var unreachable in unreachableAssets) {
+ changeSet.removedSource(unreachable);
+ unreachable.updateContents(null);
+ sources.remove(unreachable.assetId);
+ }
+
+ // Update the analyzer context with the latest sources
+ _context.applyChanges(changeSet);
+ // Force resolve each entry point (the getter will ensure the library is
+ // computed first).
+ _entryLibraries = entryPoints.map((id) {
+ var source = sources[id];
+ if (source == null) return null;
+ return _context.computeLibraryElement(source);
+ }).toList();
+ });
+ }
+
+ Iterable<LibraryElement> get libraries {
+ if (_libraries == null) {
+ // Note: we don't use `lib.visibleLibraries` because that excludes the
+ // exports seen in the entry libraries.
+ _libraries = new Set<LibraryElement>();
+ _entryLibraries.forEach(_collectLibraries);
+ }
+ return _libraries;
+ }
+
+ void _collectLibraries(LibraryElement lib) {
+ if (lib == null || _libraries.contains(lib)) return;
+ _libraries.add(lib);
+ lib.importedLibraries.forEach(_collectLibraries);
+ lib.exportedLibraries.forEach(_collectLibraries);
+ }
+
+ LibraryElement getLibraryByName(String libraryName) =>
+ libraries.firstWhere((l) => l.name == libraryName, orElse: () => null);
+
+ LibraryElement getLibraryByUri(Uri uri) =>
+ libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null);
+
+ ClassElement getType(String typeName) {
+ var dotIndex = typeName.lastIndexOf('.');
+ var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex);
+
+ var className = dotIndex == -1 ?
+ typeName : typeName.substring(dotIndex + 1);
+
+ for (var lib in libraries.where((l) => l.name == libraryName)) {
+ var type = lib.getType(className);
+ if (type != null) return type;
+ }
+ return null;
+ }
+
+ Element getLibraryVariable(String variableName) {
+ var dotIndex = variableName.lastIndexOf('.');
+ var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex);
+
+ var name = dotIndex == -1 ?
+ variableName : variableName.substring(dotIndex + 1);
+
+ return libraries.where((lib) => lib.name == libraryName)
+ .expand((lib) => lib.units)
+ .expand((unit) => unit.topLevelVariables)
+ .firstWhere((variable) => variable.name == name,
+ orElse: () => null);
+ }
+
+ Element getLibraryFunction(String fnName) {
+ var dotIndex = fnName.lastIndexOf('.');
+ var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex);
+
+ var name = dotIndex == -1 ?
+ fnName : fnName.substring(dotIndex + 1);
+
+ return libraries.where((lib) => lib.name == libraryName)
+ .expand((lib) => lib.units)
+ .expand((unit) => unit.functions)
+ .firstWhere((fn) => fn.name == name,
+ orElse: () => null);
+ }
+
+ EvaluationResult evaluateConstant(
+ LibraryElement library, Expression expression) {
+ return new ConstantEvaluator(library.source, _context.typeProvider)
+ .evaluate(expression);
+ }
+
+ Uri getImportUri(LibraryElement lib, {AssetId from}) =>
+ _getSourceUri(lib, from: from);
+
+
+ /// Similar to getImportUri but will get the part URI for parts rather than
+ /// the library URI.
+ Uri _getSourceUri(Element element, {AssetId from}) {
+ var source = element.source;
+ if (source is _AssetBasedSource) {
+ return source.getSourceUri(from);
+ } else if (source is UriAnnotatedSource) {
+ return source.uri;
+ }
+ // Should not be able to encounter any other source types.
+ throw new StateError('Unable to resolve URI for ${source.runtimeType}');
+ }
+
+ AssetId getSourceAssetId(Element element) {
+ var source = element.source;
+ if (source is _AssetBasedSource) return source.assetId;
+ return null;
+ }
+
+ SourceSpan getSourceSpan(Element element) {
+ var sourceFile = getSourceFile(element);
+ if (sourceFile == null) return null;
+ return sourceFile.span(element.node.offset, element.node.end);
+ }
+
+ TextEditTransaction createTextEditTransaction(Element element) {
+ if (element.source is! _AssetBasedSource) return null;
+
+ // Cannot edit unless there is an active transformer.
+ if (_currentTransform == null) return null;
+
+ _AssetBasedSource source = element.source;
+ // Cannot modify assets in other packages.
+ if (source.assetId.package != _currentTransform.primaryInput.id.package) {
+ return null;
+ }
+
+ var sourceFile = getSourceFile(element);
+ if (sourceFile == null) return null;
+
+ return new TextEditTransaction(source.rawContents, sourceFile);
+ }
+
+ /// Gets the SourceFile for the source of the element.
+ SourceFile getSourceFile(Element element) {
+ var assetId = getSourceAssetId(element);
+ if (assetId == null) return null;
+
+ var importUri = _getSourceUri(element);
+ var spanPath = importUri != null ? importUri.toString() : assetId.path;
+ return new SourceFile(sources[assetId].rawContents, url: spanPath);
+ }
+}
+
+/// Implementation of Analyzer's Source for Barback based assets.
+class _AssetBasedSource extends Source {
+
+ /// Asset ID where this source can be found.
+ final AssetId assetId;
+
+ /// The resolver this is being used in.
+ final ResolverImpl _resolver;
+
+ /// Cache of dependent asset IDs, to avoid re-parsing the AST.
+ Iterable<AssetId> _dependentAssets;
+
+ /// The current revision of the file, incremented only when file changes.
+ int _revision = 0;
+
+ /// The file contents.
+ String _contents;
+
+ _AssetBasedSource(this.assetId, this._resolver);
+
+ /// Update the dependencies of this source. This parses [contents] but avoids
+ /// any analyzer resolution.
+ void updateDependencies(String contents) {
+ if (contents == _contents) return;
+ var unit = parseDirectives(contents, suppressErrors: true);
+ _dependentAssets = unit.directives
+ .where((d) => (d is ImportDirective || d is PartDirective ||
+ d is ExportDirective))
+ .map((d) => _resolve(assetId, d.uri.stringValue, _logger,
+ _getSpan(d, contents)))
+ .where((id) => id != null).toSet();
+ }
+
+ /// Update the contents of this file with [contents].
+ ///
+ /// Returns true if the contents of this asset have changed.
+ bool updateContents(String contents) {
+ if (contents == _contents) return false;
+ _contents = contents;
+ ++_revision;
+ return true;
+ }
+
+ /// Contents of the file.
+ TimestampedData<String> get contents {
+ if (!exists()) throw new StateError('$assetId does not exist');
+
+ return new TimestampedData<String>(modificationStamp, _contents);
+ }
+
+ /// Contents of the file.
+ String get rawContents => _contents;
+
+ Uri get uri => Uri.parse('asset:${assetId.package}/${assetId.path}');
+
+ /// Logger for the current transform.
+ ///
+ /// Only valid while the resolver is updating assets.
+ TransformLogger get _logger => _resolver._currentTransform.logger;
+
+ /// Gets all imports/parts/exports which resolve to assets (non-Dart files).
+ Iterable<AssetId> get dependentAssets => _dependentAssets;
+
+ bool exists() => _contents != null;
+
+ bool operator ==(Object other) =>
+ other is _AssetBasedSource && assetId == other.assetId;
+
+ int get hashCode => assetId.hashCode;
+
+ void getContentsToReceiver(Source_ContentReceiver receiver) {
+ receiver.accept(rawContents, modificationStamp);
+ }
+
+ String get encoding =>
+ "${uriKind.encoding}${assetId.package}/${assetId.path}";
+
+ String get fullName => assetId.toString();
+
+ int get modificationStamp => _revision;
+
+ String get shortName => path.basename(assetId.path);
+
+ UriKind get uriKind {
+ if (assetId.path.startsWith('lib/')) return UriKind.PACKAGE_URI;
+ return UriKind.FILE_URI;
+ }
+
+ bool get isInSystemLibrary => false;
+
+ Source resolveRelative(Uri relativeUri) {
+ var id = _resolve(assetId, relativeUri.toString(), _logger, null);
+ if (id == null) return null;
+
+ // The entire AST should have been parsed and loaded at this point.
+ var source = _resolver.sources[id];
+ if (source == null) {
+ _logger.error('Could not load asset $id');
+ }
+ return source;
+ }
+
+ Uri resolveRelativeUri(Uri relativeUri) {
+ var id = _resolve(assetId, relativeUri.toString(), _logger, null);
+ if (id == null) return uri.resolveUri(relativeUri);
+
+ // The entire AST should have been parsed and loaded at this point.
+ var source = _resolver.sources[id];
+ if (source == null) {
+ _logger.error('Could not load asset $id');
+ }
+ return source.uri;
+ }
+
+ /// For logging errors.
+ SourceSpan _getSpan(AstNode node, [String contents]) =>
+ _getSourceFile(contents).span(node.offset, node.end);
+ /// For logging errors.
+ SourceFile _getSourceFile([String contents]) {
+ var uri = getSourceUri();
+ var path = uri != null ? uri.toString() : assetId.path;
+ return new SourceFile(contents != null ? contents : rawContents, url: path);
+ }
+
+ /// Gets a URI which would be appropriate for importing this file.
+ ///
+ /// Note that this file may represent a non-importable file such as a part.
+ Uri getSourceUri([AssetId from]) {
+ if (!assetId.path.startsWith('lib/')) {
+ // Cannot do absolute imports of non lib-based assets.
+ if (from == null) return null;
+
+ if (assetId.package != from.package) return null;
+ return new Uri(
+ path: path.relative(assetId.path, from: path.dirname(from.path)));
+ }
+
+ return Uri.parse('package:${assetId.package}/${assetId.path.substring(4)}');
+ }
+}
+
+/// Implementation of Analyzer's UriResolver for Barback based assets.
+class _AssetUriResolver implements UriResolver {
+ final ResolverImpl _resolver;
+ _AssetUriResolver(this._resolver);
+
+ Source resolveAbsolute(Uri uri) {
+ assert(uri.scheme != 'dart');
+ var assetId;
+ if (uri.scheme == 'asset') {
+ var parts = path.split(uri.path);
+ assetId = new AssetId(parts[0], path.joinAll(parts.skip(1)));
+ } else {
+ assetId = _resolve(null, uri.toString(), logger, null);
+ if (assetId == null) {
+ logger.error('Unable to resolve asset ID for "$uri"');
+ return null;
+ }
+ }
+ var source = _resolver.sources[assetId];
+ // Analyzer expects that sources which are referenced but do not exist yet
+ // still exist, so just make an empty source.
+ if (source == null) {
+ source = new _AssetBasedSource(assetId, _resolver);
+ _resolver.sources[assetId] = source;
+ }
+ return source;
+ }
+
+ Source fromEncoding(UriKind kind, Uri uri) =>
+ throw new UnsupportedError('fromEncoding is not supported');
+
+ Uri restoreAbsolute(Source source) =>
+ throw new UnsupportedError('restoreAbsolute is not supported');
+
+ TransformLogger get logger => _resolver._currentTransform.logger;
+}
+
+/// Get an asset ID for a URL relative to another source asset.
+AssetId _resolve(AssetId source, String url, TransformLogger logger,
+ SourceSpan span) {
+ if (url == null || url == '') return null;
+ var uri = Uri.parse(url);
+
+ // Workaround for dartbug.com/17156- pub transforms package: imports from
+ // files of the transformers package to have absolute /packages/ URIs.
+ if (uri.scheme == '' && path.isAbsolute(url)
+ && uri.pathSegments[0] == 'packages') {
+ uri = Uri.parse('package:${uri.pathSegments.skip(1).join(path.separator)}');
+ }
+
+ if (uri.scheme == 'package') {
+ var segments = new List.from(uri.pathSegments);
+ var package = segments[0];
+ segments[0] = 'lib';
+ return new AssetId(package, segments.join(path.separator));
+ }
+ // Dart SDK libraries do not have assets.
+ if (uri.scheme == 'dart') return null;
+
+ return uriToAssetId(source, url, logger, span);
+}
+
+
+/// A completer that waits until all added [Future]s complete.
+// TODO(blois): Copied from quiver. Remove from here when it gets
+// added to dart:core. (See #6626.)
+class FutureGroup<E> {
+ static const _FINISHED = -1;
+
+ int _pending = 0;
+ Future _failedTask;
+ final Completer<List> _completer = new Completer<List>();
+ final List results = [];
+
+ /** Gets the task that failed, if any. */
+ Future get failedTask => _failedTask;
+
+ /**
+ * Wait for [task] to complete.
+ *
+ * If this group has already been marked as completed, a [StateError] will be
+ * thrown.
+ *
+ * If this group has a [failedTask], new tasks will be ignored, because the
+ * error has already been signaled.
+ */
+ void add(Future task) {
+ if (_failedTask != null) return;
+ if (_pending == _FINISHED) throw new StateError("Future already completed");
+
+ _pending++;
+ var i = results.length;
+ results.add(null);
+ task.then((res) {
+ results[i] = res;
+ if (_failedTask != null) return;
+ _pending--;
+ if (_pending == 0) {
+ _pending = _FINISHED;
+ _completer.complete(results);
+ }
+ }, onError: (e, s) {
+ if (_failedTask != null) return;
+ _failedTask = task;
+ _completer.completeError(e, s);
+ });
+ }
+
+ /**
+ * A Future that completes with a List of the values from all the added
+ * tasks, when they have all completed.
+ *
+ * If any task fails, this Future will receive the error. Only the first
+ * error will be sent to the Future.
+ */
+ Future<List<E>> get future => _completer.future;
+}
+
+/// A pending update to notify the resolver that a [Source] has been added or
+/// changed. This is used by the `_performResolve` algorithm above to apply all
+/// changes after it first discovers the transitive closure of files that are
+/// reachable from the sources.
+class _PendingUpdate {
+ _AssetBasedSource source;
+ String content;
+
+ _PendingUpdate(this.source, this.content);
+
+ void apply(ChangeSet changeSet) {
+ if (!source.updateContents(content)) return;
+ if (source._revision == 1 && source._contents != null) {
+ changeSet.addedSource(source);
+ } else {
+ changeSet.changedSource(source);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698