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

Unified Diff: pkg/code_transformers/lib/src/resolver_impl.dart

Issue 140203007: Adding package:code_transformers for unifying common transformers code (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months 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: pkg/code_transformers/lib/src/resolver_impl.dart
diff --git a/pkg/code_transformers/lib/src/resolver_impl.dart b/pkg/code_transformers/lib/src/resolver_impl.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5918c95933ee4e2aa5c48472aa5b2fb94bc0336a
--- /dev/null
+++ b/pkg/code_transformers/lib/src/resolver_impl.dart
@@ -0,0 +1,492 @@
+// 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/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/error.dart';
+import 'package:analyzer/src/generated/java_io.dart';
+import 'package:analyzer/src/generated/parser.dart' show Parser;
+import 'package:analyzer/src/generated/scanner.dart';
+import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
+import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:barback/barback.dart';
+import 'package:path/path.dart' as path;
+import 'package:source_maps/refactor.dart';
+import 'package:source_maps/span.dart' show SourceFile, Span;
+
+import 'resolver.dart';
+
+/// 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>{};
+ /// The Dart entry point file where parsing begins.
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 nit: add empty line above comment
blois 2014/02/19 21:42:58 Done.
+ final AssetId entryPoint;
+
+ final AnalysisContext _context =
+ AnalysisEngine.instance.createAnalysisContext();
+
+ /// Transform for which this is currently updating, or null when not updating.
+ Transform _currentTransform;
+ /// The currently resolved library, or null if unresolved.
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 same here and below
blois 2014/02/19 21:42:58 Done.
+ LibraryElement _entryLibrary;
+ /// Handler for all Dart SDK (dart:) sources.
+ DirectoryBasedDartSdk _dartSdk;
+
+ /// Creates a resolver that will resolve the Dart code starting at
+ /// [entryPoint].
+ ///
+ /// [sdkDir] is the root directory of the Dart SDK, for resolving dart:
+ /// imports.
+ ResolverImpl(this.entryPoint, String sdkDir, {AnalysisOptions options}) {
+ if (options == null) {
+ options = new AnalysisOptionsImpl()
+ ..cacheSize = 256 // # of sources to cache ASTs for.
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 can we make it bigger :)?
blois 2014/02/19 21:42:58 I actually just cloned this from Angular's extract
+ ..preserveComments = false
+ ..analyzeFunctionBodies = true;
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 should this be an option too? Seems like a common
blois 2014/02/19 21:42:58 Added, but the options themselves can be provided
Siggi Cherem (dart-lang) 2014/02/19 22:36:06 Ah - good point, I overlooked that if options is p
+ }
+ _context.analysisOptions = options;
+
+ _dartSdk = new _DirectoryBasedDartSdkProxy(new JavaFile(sdkDir));
+ _dartSdk.context.analysisOptions = options;
+
+ _context.sourceFactory = new SourceFactory.con2([
+ new DartUriResolverProxy(_dartSdk),
+ new _AssetUriResolver(this)]);
+ }
+
+ LibraryElement get entryLibrary => _entryLibrary;
+
+
+ /// Update the status of all the sources referenced by the entryPoint and
+ /// update the resolved library.
+ ///
+ /// This will be invoked automatically by [ResolverTransformer]. Only one
+ /// transformer may update this at a time.
+ Future updateSources(Transform transform) {
+ if (_currentTransform != null) {
+ throw new StateError('Cannot be accessed by concurrent transforms');
+ }
+ _currentTransform = transform;
+ // Clear this out and update once all asset changes have been processed.
+ _entryLibrary = null;
+
+ // 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 toVisit = new Set<AssetId>();
+ var changedSources = <Source>[];
+ var addedSources = <Source>[];
+ var removedSources = <Source>[];
+ toVisit.add(entryPoint);
+
+ Future visitNext() {
+ if (toVisit.length == 0) return null;
+
+ var assetId = toVisit.first;
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 might be cheaper to use a queue instead of a Set f
blois 2014/02/19 21:42:58 Since order is not critical better to optimize for
Siggi Cherem (dart-lang) 2014/02/19 22:36:06 oh - I think you can remove the check for 'toVisit
blois 2014/02/20 01:03:12 Ended up switching over to FutureGroup which allow
+ toVisit.remove(assetId);
+ visited.add(assetId);
+
+ return transform.readInputAsString(assetId).then((contents) {
+ var source = sources[assetId];
+ if (source == null) {
+ source = new _AssetBasedSource(assetId, this);
+ sources[assetId] = source;
+ addedSources.add(source);
+ }
+ var changed = source.updateContents(contents);
+ if (changed) {
+ changedSources.add(source);
+ }
+
+ for (var id in source.dependentAssets) {
+ if (!visited.contains(id) && !toVisit.contains(id)) {
+ toVisit.add(id);
+ }
+ }
+
+ return visitNext();
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 FYI - this is fine, but it might be worth using Fu
blois 2014/02/19 21:42:58 Would prefer to leave as-is for now, though switch
+ }, onError: (e) {
+ removedSources.add(sources[assetId]);
+ return visitNext();
+ });
+ }
+
+ // Once we have all asset sources updated with the new contents then
+ // resolve everything.
+ return new Future(visitNext).then((_) {
+ ChangeSet changeSet = new ChangeSet();
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 -> var
blois 2014/02/19 21:42:58 Done.
+
+ // Add all sources which were not reached to the removed set.
+ removedSources.addAll(new Set.from(sources.keys)
+ .difference(visited)
+ .map((id) => sources[id]));
+
+ // Clear out removed sources from our cache.
+ removedSources.map((source) => source.assetId).forEach(sources.remove);
+
+ addedSources.forEach(changeSet.added);
+ changedSources.forEach(changeSet.changed);
+ removedSources.forEach(changeSet.removed);
+
+ // Update the analyzer context with the latest sources
+ _context.applyChanges(changeSet);
+ // Resolve the AST
+ _entryLibrary = _context.computeLibraryElement(sources[entryPoint]);
+
+ _currentTransform = null;
+ });
+ }
+
+ Iterable<LibraryElement> get libraries => entryLibrary.visibleLibraries;
+
+ 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);
+ }
+
+ Uri getImportUri(LibraryElement lib, {AssetId from}) {
+ var source = lib.source;
+ if (source is _AssetBasedSource) {
+ var id = source.assetId;
+
+ if (!id.path.startsWith('lib/')) {
+ // Cannot do absolute imports of non lib-based assets.
+ if (from == null) return null;
+
+ if (id.package != from.package) return null;
+ return new Uri(
+ path: path.relative(id.path, from: path.dirname(from.path)));
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 remove extra space
blois 2014/02/19 21:42:58 Done.
+ }
+
+ return Uri.parse('package:${id.package}/${id.path.substring(4)}');
+ } else if (source is _DartSourceProxy) {
+ 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;
+ }
+
+ Span getSourceSpan(Element element) {
+ var assetId = getSourceAssetId(element);
+ if (assetId == null) return null;
+
+ var sourceFile = new SourceFile.text(assetId.path,
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 let's change this first argument to match what we
blois 2014/02/19 21:42:58 Done.
+ sources[assetId].contents);
+ return sourceFile.span(element.node.offset, element.node.end);
+ }
+
+ TextEditTransaction createTextEditTransaction(Element element) {
+ if (element.source is! _AssetBasedSource) return null;
+
+ _AssetBasedSource source = element.source;
+ // Cannot modify assets in other packages.
+ if (source.assetId.package != entryPoint.package) return null;
+
+ var sourceFile = new SourceFile.text(source.assetId.path, source.contents);
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 same here
blois 2014/02/19 21:42:58 Done.
+ return new TextEditTransaction(source.contents, sourceFile);
+ }
+}
+
+/// 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.
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 similar style nit here: add empty line above each
blois 2014/02/19 21:42:58 Done.
+ 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 contents of this file with [contents].
+ ///
+ /// Returns true if the contents of this asset have changed.
+ bool updateContents(String contents) {
+ if (contents != _contents) {
+ _contents = contents;
+ ++_revision;
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 _revision++? It's style nit, I think we use i++ m
blois 2014/02/19 21:42:58 Old habits die hard. Prefix operator is sometimes
Siggi Cherem (dart-lang) 2014/02/19 22:36:06 Yeah, I recall that was the case in C++, good thin
+ // Invalidate the imports so we only parse the AST when needed.
+ _dependentAssets = null;
+ return true;
+ }
+ return false;
+ }
+
+ /// Contents of the file.
+ String get contents => _contents;
+
+ /// 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 {
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 since you already have a cache of _AssetBasedSourc
+ // Use the cached imports if we have them.
+ if (_dependentAssets != null) return _dependentAssets;
+
+ var errorListener = new _ErrorCollector();
+ var reader = new CharSequenceReader(contents);
+ var scanner = new Scanner(null, reader, errorListener);
+ var token = scanner.tokenize();
+ var parser = new Parser(null, errorListener);
+
+ var compilationUnit = parser.parseCompilationUnit(token);
+
+ // Walk the AST looking for import/export/part directives.
+ _dependentAssets = compilationUnit.directives
+ .where((d) => (d is ImportDirective || d is PartDirective ||
+ d is ExportDirective))
+ .map((d) => _resolve(assetId, d.uri.stringValue,
+ _logger, _getSpan(d)))
+ .where((id) => id != null);
+
+ return _dependentAssets;
+ }
+
+ bool exists() => true;
+
+ bool operator ==(Object other) =>
+ other is _AssetBasedSource && assetId == other.assetId;
+
+ int get hashCode => assetId.hashCode;
+
+ void getContents(Source_ContentReceiver receiver) {
+ receiver.accept(contents, 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;
+ }
+
+ /// For logging errors.
+ Span _getSpan(ASTNode node) => _sourceFile.span(node.offset, node.end);
+ /// For logging errors.
+ SourceFile get _sourceFile => new SourceFile.text(assetId.path, contents);
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 same here: use something like spanUriFor(...)
blois 2014/02/19 21:42:58 Done.
+}
+
+/// Implementation of Analyzer's UriResolver for Barback based assets.
+class _AssetUriResolver implements UriResolver {
+ final ResolverImpl _resolver;
+ _AssetUriResolver(this._resolver);
+
+ Source resolveAbsolute(ContentCache contentCache, Uri uri) {
+ var assetId = _resolve(null, uri.toString(), logger, null);
+ var source = _resolver.sources[assetId];
+ /// All resolved assets should be available by this point.
+ if (source == null) {
+ logger.error('Unable to find asset for "$uri"');
+ }
+ return source;
+ }
+
+ Source fromEncoding(ContentCache contentCache, 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;
+}
+
+
+/// Dart SDK which wraps all Dart sources to ensure they are tracked with Uris.
+///
+/// Just a simple wrapper to make it easy to make sure that all sources we
+/// encounter are either _AssetBasedSource or _DartSourceProxy.
+class _DirectoryBasedDartSdkProxy extends DirectoryBasedDartSdk {
+ _DirectoryBasedDartSdkProxy(JavaFile sdkDirectory) : super(sdkDirectory);
+
+ Source mapDartUri(String dartUri) =>
+ _DartSourceProxy.wrap(super.mapDartUri(dartUri), Uri.parse(dartUri));
+}
+
+
+/// Dart SDK resolver which wraps all Dart sources to ensure they are tracked
+/// with URIs.
+class DartUriResolverProxy implements DartUriResolver {
+ final DartUriResolver _proxy;
+ DartUriResolverProxy(DirectoryBasedDartSdk sdk) :
+ _proxy = new DartUriResolver(sdk);
+
+ Source resolveAbsolute(ContentCache contentCache, Uri uri) =>
+ _DartSourceProxy.wrap(_proxy.resolveAbsolute(contentCache, uri), uri);
+
+ DartSdk get dartSdk => _proxy.dartSdk;
+
+ Source fromEncoding(ContentCache contentCache, UriKind kind, Uri uri) =>
+ throw new UnsupportedError('fromEncoding is not supported');
+
+ Uri restoreAbsolute(Source source) =>
+ throw new UnsupportedError('restoreAbsolute is not supported');
+}
+
+/// Source file for dart: sources which track the sources with dart: URIs.
+///
+/// This is primarily to support [Resolver.getImportUri] for Dart SDK (dart:)
+/// based libraries.
+class _DartSourceProxy implements Source {
+ /// Absolute URI which this source can be imported from
+ final Uri uri;
+ /// Underlying source object.
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 +line
blois 2014/02/19 21:42:58 Done.
+ final Source _proxy;
+
+ _DartSourceProxy(this._proxy, this.uri);
+
+ /// Ensures that [source] is a _DartSourceProxy.
+ static _DartSourceProxy wrap(Source source, Uri uri) {
+ if (source == null || source is _DartSourceProxy) return source;
+ return new _DartSourceProxy(source, uri);
+ }
+
+ Source resolveRelative(Uri relativeUri) {
+ // Assume that the type can be accessed via this URI, since these
+ // should only be parts for dart core files.
+ return wrap(_proxy.resolveRelative(relativeUri), uri);
+ }
+
+ bool exists() => _proxy.exists();
+
+ bool operator ==(Object other) =>
+ (other is _DartSourceProxy && _proxy == other._proxy);
+
+ int get hashCode => _proxy.hashCode;
+
+ void getContents(Source_ContentReceiver receiver) {
+ _proxy.getContents(receiver);
+ }
+
+ String get encoding => _proxy.encoding;
+
+ String get fullName => _proxy.fullName;
+
+ int get modificationStamp => _proxy.modificationStamp;
+
+ String get shortName => _proxy.shortName;
+
+ UriKind get uriKind => _proxy.uriKind;
+
+ bool get isInSystemLibrary => _proxy.isInSystemLibrary;
+}
+
+
+class _ErrorCollector extends AnalysisErrorListener {
+ final errors = <AnalysisError>[];
+ onError(error) => errors.add(error);
+}
+
+/// Get an asset ID for a URL relative to another source asset.
+AssetId _resolve(AssetId source, String url, TransformLogger logger,
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 this seems similar to what we have in common, is t
blois 2014/02/19 21:42:58 It's actually slightly different- this goes from a
+ Span span) {
+ if (url == null || url == '') return null;
+ var urlBuilder = path.url;
+ var uri = Uri.parse(url);
+
+ if (uri.scheme == 'package') {
+ var segments = new List.from(uri.pathSegments);
+ var package = segments[0];
+ segments[0] = 'lib';
+ return new AssetId(package, segments.join(urlBuilder.separator));
+ }
+ // Dart SDK libraries do not have assets.
+ if (uri.scheme == 'dart') {
Siggi Cherem (dart-lang) 2014/02/14 02:13:37 style nit: fast-exit style if (uri.scheme == 'dar
blois 2014/02/19 21:42:58 Done.
+ return null;
+ }
+
+ if (uri.host != '' || uri.scheme != '' || urlBuilder.isAbsolute(url)) {
+ logger.error('absolute paths not allowed: "$url"', span: span);
+ return null;
+ }
+
+ var targetPath = urlBuilder.normalize(
+ urlBuilder.join(urlBuilder.dirname(source.path), url));
+ return new AssetId(source.package, targetPath);
+}

Powered by Google App Engine
This is Rietveld 408576698