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

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

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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 unified diff | Download patch
« no previous file with comments | « code_transformers/lib/src/resolver.dart ('k') | code_transformers/lib/src/resolvers.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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'
11 show ConstantEvaluator, 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 = <AssetId, _AssetBasedSource>{
35 };
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 =
66 new SourceFactory([dartUriResolver, 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
125 .where((id) => !visited.contains(id))
126 .forEach(processAsset);
127 }, onError: (e) {
128 var source = sources[assetId];
129 if (source != null && source.exists()) {
130 _context.applyChanges(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 =
143 sources.keys.toSet().difference(visited).map((id) => sources[id]);
144 for (var unreachable in unreachableAssets) {
145 changeSet.removedSource(unreachable);
146 unreachable.updateContents(null);
147 sources.remove(unreachable.assetId);
148 }
149
150 // Update the analyzer context with the latest sources
151 _context.applyChanges(changeSet);
152 // Force resolve each entry point (the getter will ensure the library is
153 // computed first).
154 _entryLibraries = entryPoints.map((id) {
155 var source = sources[id];
156 if (source == null) return null;
157 return _context.computeLibraryElement(source);
158 }).toList();
159 });
160 }
161
162 Iterable<LibraryElement> get libraries {
163 if (_libraries == null) {
164 // Note: we don't use `lib.visibleLibraries` because that excludes the
165 // exports seen in the entry libraries.
166 _libraries = new Set<LibraryElement>();
167 _entryLibraries.forEach(_collectLibraries);
168 }
169 return _libraries;
170 }
171
172 void _collectLibraries(LibraryElement lib) {
173 if (lib == null || _libraries.contains(lib)) return;
174 _libraries.add(lib);
175 lib.importedLibraries.forEach(_collectLibraries);
176 lib.exportedLibraries.forEach(_collectLibraries);
177 }
178
179 LibraryElement getLibraryByName(String libraryName) =>
180 libraries.firstWhere((l) => l.name == libraryName, orElse: () => null);
181
182 LibraryElement getLibraryByUri(Uri uri) =>
183 libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null);
184
185 ClassElement getType(String typeName) {
186 var dotIndex = typeName.lastIndexOf('.');
187 var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex);
188
189 var className =
190 dotIndex == -1 ? typeName : typeName.substring(dotIndex + 1);
191
192 for (var lib in libraries.where((l) => l.name == libraryName)) {
193 var type = lib.getType(className);
194 if (type != null) return type;
195 }
196 return null;
197 }
198
199 Element getLibraryVariable(String variableName) {
200 var dotIndex = variableName.lastIndexOf('.');
201 var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex);
202
203 var name =
204 dotIndex == -1 ? variableName : variableName.substring(dotIndex + 1);
205
206 return libraries
207 .where((lib) => lib.name == libraryName)
208 .expand((lib) => lib.units)
209 .expand((unit) => unit.topLevelVariables)
210 .firstWhere((variable) => variable.name == name, orElse: () => null);
211 }
212
213 Element getLibraryFunction(String fnName) {
214 var dotIndex = fnName.lastIndexOf('.');
215 var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex);
216
217 var name = dotIndex == -1 ? fnName : fnName.substring(dotIndex + 1);
218
219 return libraries
220 .where((lib) => lib.name == libraryName)
221 .expand((lib) => lib.units)
222 .expand((unit) => unit.functions)
223 .firstWhere((fn) => fn.name == name, orElse: () => null);
224 }
225
226 EvaluationResult evaluateConstant(
227 LibraryElement library, Expression expression) {
228 return new ConstantEvaluator(library.source, _context.typeProvider)
229 .evaluate(expression);
230 }
231
232 Uri getImportUri(LibraryElement lib, {AssetId from}) =>
233 _getSourceUri(lib, from: from);
234
235 /// Similar to getImportUri but will get the part URI for parts rather than
236 /// the library URI.
237 Uri _getSourceUri(Element element, {AssetId from}) {
238 var source = element.source;
239 if (source is _AssetBasedSource) {
240 var uriString = assetIdToUri(source.assetId, from: from);
241 return uriString != null ? Uri.parse(uriString) : null;
242 } else if (source is UriAnnotatedSource) {
243 return source.uri;
244 }
245 // Should not be able to encounter any other source types.
246 throw new StateError('Unable to resolve URI for ${source.runtimeType}');
247 }
248
249 AssetId getSourceAssetId(Element element) {
250 var source = element.source;
251 if (source is _AssetBasedSource) return source.assetId;
252 return null;
253 }
254
255 SourceSpan getSourceSpan(Element element) {
256 var sourceFile = getSourceFile(element);
257 if (sourceFile == null) return null;
258 return sourceFile.span(element.node.offset, element.node.end);
259 }
260
261 TextEditTransaction createTextEditTransaction(Element element) {
262 if (element.source is! _AssetBasedSource) return null;
263
264 // Cannot edit unless there is an active transformer.
265 if (_currentTransform == null) return null;
266
267 _AssetBasedSource source = element.source;
268 // Cannot modify assets in other packages.
269 if (source.assetId.package != _currentTransform.primaryInput.id.package) {
270 return null;
271 }
272
273 var sourceFile = getSourceFile(element);
274 if (sourceFile == null) return null;
275
276 return new TextEditTransaction(source.rawContents, sourceFile);
277 }
278
279 /// Gets the SourceFile for the source of the element.
280 SourceFile getSourceFile(Element element) {
281 var assetId = getSourceAssetId(element);
282 if (assetId == null) return null;
283
284 var importUri = _getSourceUri(element);
285 var spanPath = importUri != null ? importUri.toString() : assetId.path;
286 return new SourceFile(sources[assetId].rawContents, url: spanPath);
287 }
288 }
289
290 /// Implementation of Analyzer's Source for Barback based assets.
291 class _AssetBasedSource extends Source {
292
293 /// Asset ID where this source can be found.
294 final AssetId assetId;
295
296 /// The resolver this is being used in.
297 final ResolverImpl _resolver;
298
299 /// Cache of dependent asset IDs, to avoid re-parsing the AST.
300 Iterable<AssetId> _dependentAssets;
301
302 /// The current revision of the file, incremented only when file changes.
303 int _revision = 0;
304
305 /// The file contents.
306 String _contents;
307
308 _AssetBasedSource(this.assetId, this._resolver);
309
310 /// Update the dependencies of this source. This parses [contents] but avoids
311 /// any analyzer resolution.
312 void updateDependencies(String contents) {
313 if (contents == _contents) return;
314 var unit = parseDirectives(contents, suppressErrors: true);
315 _dependentAssets = unit.directives
316 .where((d) => (d is ImportDirective ||
317 d is PartDirective ||
318 d is ExportDirective))
319 .map((d) => _resolve(
320 assetId, d.uri.stringValue, _logger, _getSpan(d, contents)))
321 .where((id) => id != null)
322 .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 = assetIdToUri(assetId);
412 var path = uri != null ? uri : assetId.path;
413 return new SourceFile(contents != null ? contents : rawContents, url: path);
414 }
415 }
416
417 /// Implementation of Analyzer's UriResolver for Barback based assets.
418 class _AssetUriResolver implements UriResolver {
419 final ResolverImpl _resolver;
420 _AssetUriResolver(this._resolver);
421
422 Source resolveAbsolute(Uri uri) {
423 assert(uri.scheme != 'dart');
424 var assetId;
425 if (uri.scheme == 'asset') {
426 var parts = path.split(uri.path);
427 assetId = new AssetId(parts[0], path.joinAll(parts.skip(1)));
428 } else {
429 assetId = _resolve(null, uri.toString(), logger, null);
430 if (assetId == null) {
431 logger.error('Unable to resolve asset ID for "$uri"');
432 return null;
433 }
434 }
435 var source = _resolver.sources[assetId];
436 // Analyzer expects that sources which are referenced but do not exist yet
437 // still exist, so just make an empty source.
438 if (source == null) {
439 source = new _AssetBasedSource(assetId, _resolver);
440 _resolver.sources[assetId] = source;
441 }
442 return source;
443 }
444
445 Source fromEncoding(UriKind kind, Uri uri) =>
446 throw new UnsupportedError('fromEncoding is not supported');
447
448 Uri restoreAbsolute(Source source) =>
449 throw new UnsupportedError('restoreAbsolute is not supported');
450
451 TransformLogger get logger => _resolver._currentTransform.logger;
452 }
453
454 /// Get an asset ID for a URL relative to another source asset.
455 AssetId _resolve(
456 AssetId source, String url, TransformLogger logger, SourceSpan span) {
457 if (url == null || url == '') return null;
458 var uri = Uri.parse(url);
459
460 // Workaround for dartbug.com/17156- pub transforms package: imports from
461 // files of the transformers package to have absolute /packages/ URIs.
462 if (uri.scheme == '' &&
463 path.isAbsolute(url) &&
464 uri.pathSegments[0] == 'packages') {
465 uri = Uri.parse('package:${uri.pathSegments.skip(1).join(path.separator)}');
466 }
467
468 if (uri.scheme == 'package') {
469 var segments = new List.from(uri.pathSegments);
470 var package = segments[0];
471 segments[0] = 'lib';
472 return new AssetId(package, segments.join(path.separator));
473 }
474 // Dart SDK libraries do not have assets.
475 if (uri.scheme == 'dart') return null;
476
477 return uriToAssetId(source, url, logger, span);
478 }
479
480 /// A completer that waits until all added [Future]s complete.
481 // TODO(blois): Copied from quiver. Remove from here when it gets
482 // added to dart:core. (See #6626.)
483 class FutureGroup<E> {
484 static const _FINISHED = -1;
485
486 int _pending = 0;
487 Future _failedTask;
488 final Completer<List> _completer = new Completer<List>();
489 final List results = [];
490
491 /** Gets the task that failed, if any. */
492 Future get failedTask => _failedTask;
493
494 /**
495 * Wait for [task] to complete.
496 *
497 * If this group has already been marked as completed, a [StateError] will be
498 * thrown.
499 *
500 * If this group has a [failedTask], new tasks will be ignored, because the
501 * error has already been signaled.
502 */
503 void add(Future task) {
504 if (_failedTask != null) return;
505 if (_pending == _FINISHED) throw new StateError("Future already completed");
506
507 _pending++;
508 var i = results.length;
509 results.add(null);
510 task.then((res) {
511 results[i] = res;
512 if (_failedTask != null) return;
513 _pending--;
514 if (_pending == 0) {
515 _pending = _FINISHED;
516 _completer.complete(results);
517 }
518 }, onError: (e, s) {
519 if (_failedTask != null) return;
520 _failedTask = task;
521 _completer.completeError(e, s);
522 });
523 }
524
525 /**
526 * A Future that completes with a List of the values from all the added
527 * tasks, when they have all completed.
528 *
529 * If any task fails, this Future will receive the error. Only the first
530 * error will be sent to the Future.
531 */
532 Future<List<E>> get future => _completer.future;
533 }
534
535 /// A pending update to notify the resolver that a [Source] has been added or
536 /// changed. This is used by the `_performResolve` algorithm above to apply all
537 /// changes after it first discovers the transitive closure of files that are
538 /// reachable from the sources.
539 class _PendingUpdate {
540 _AssetBasedSource source;
541 String content;
542
543 _PendingUpdate(this.source, this.content);
544
545 void apply(ChangeSet changeSet) {
546 if (!source.updateContents(content)) return;
547 if (source._revision == 1 && source._contents != null) {
548 changeSet.addedSource(source);
549 } else {
550 changeSet.changedSource(source);
551 }
552 }
553 }
OLDNEW
« no previous file with comments | « code_transformers/lib/src/resolver.dart ('k') | code_transformers/lib/src/resolvers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698