refactor(dart/transform): Simplify deferred rewriting
Simplify the DeferredRewriting and move package:quiver to a dev dependency. Closes #6994
This commit is contained in:
parent
eb688f2c8e
commit
1fd924f7d5
|
@ -18,12 +18,12 @@ dependencies:
|
||||||
logging: '>=0.9.0 <0.12.0'
|
logging: '>=0.9.0 <0.12.0'
|
||||||
observe: '^0.13.1'
|
observe: '^0.13.1'
|
||||||
protobuf: '^0.5.0'
|
protobuf: '^0.5.0'
|
||||||
quiver: '^0.21.4'
|
|
||||||
source_span: '^1.0.0'
|
source_span: '^1.0.0'
|
||||||
stack_trace: '^1.1.1'
|
stack_trace: '^1.1.1'
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
code_transformers: '>=0.2.9+4 <0.4.0'
|
code_transformers: '>=0.2.9+4 <0.4.0'
|
||||||
guinness: '^0.1.18'
|
guinness: '^0.1.18'
|
||||||
|
quiver: '^0.21.4'
|
||||||
test: '^0.12.6'
|
test: '^0.12.6'
|
||||||
transformers:
|
transformers:
|
||||||
- angular2
|
- angular2
|
||||||
|
|
|
@ -4,160 +4,176 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:analyzer/analyzer.dart';
|
import 'package:analyzer/analyzer.dart';
|
||||||
import 'package:analyzer/src/generated/ast.dart';
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
|
||||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||||
import 'package:angular2/src/transform/common/logging.dart';
|
import 'package:angular2/src/transform/common/logging.dart';
|
||||||
import 'package:angular2/src/transform/common/names.dart';
|
import 'package:angular2/src/transform/common/names.dart';
|
||||||
import 'package:angular2/src/transform/common/url_resolver.dart';
|
import 'package:angular2/src/transform/common/url_resolver.dart';
|
||||||
import 'package:barback/barback.dart';
|
|
||||||
import 'package:quiver/iterables.dart' as it;
|
|
||||||
|
|
||||||
class Rewriter {
|
/// Rewrites `loadLibrary` calls to initialize libraries once loaded.
|
||||||
final AssetId _entryPoint;
|
///
|
||||||
final AssetReader _reader;
|
/// 1. Finds all the deferred library imports and loadLibrary invocations in
|
||||||
final _FindDeferredLibraries _visitor;
|
/// `_entryPoint`
|
||||||
|
/// 2. Removes any libraries that don't require angular codegen.
|
||||||
|
/// 3. For the remaining libraries, rewrites the import to the corresponding
|
||||||
|
/// `.template.dart` file.
|
||||||
|
/// 4. Chains a future to the `loadLibrary` call which initializes the
|
||||||
|
/// library.
|
||||||
|
///
|
||||||
|
/// To the extent possible, this method does not change line numbers or
|
||||||
|
/// offsets in the provided code to facilitate debugging via source maps.
|
||||||
|
Future<String> rewriteLibrary(AssetId entryPoint, AssetReader reader) async {
|
||||||
|
var code = await reader.readAsString(entryPoint);
|
||||||
|
|
||||||
Rewriter(AssetId entryPoint, AssetReader reader)
|
return logElapsedAsync(() async {
|
||||||
: _entryPoint = entryPoint,
|
// If we can determine there are no deferred libraries, avoid additional
|
||||||
_reader = reader,
|
// parsing the entire file and bail early.
|
||||||
_visitor = new _FindDeferredLibraries(reader, entryPoint);
|
var onlyDirectives = parseDirectives(code, name: entryPoint.path);
|
||||||
|
if (onlyDirectives == null) {
|
||||||
|
log.fine('No directives parsed, bailing early.', asset: entryPoint);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Rewrites `loadLibrary` calls to initialize libraries once loaded.
|
final importVisitor = new _FindDeferredLibraries(reader, entryPoint);
|
||||||
///
|
onlyDirectives.directives.accept(importVisitor);
|
||||||
/// 1. Finds all the deferred library imports and loadLibrary invocations in
|
|
||||||
/// `_entryPoint`
|
|
||||||
/// 2. Removes any libraries that don't require angular codegen.
|
|
||||||
/// 3. For the remaining libraries, rewrites the import to the corresponding
|
|
||||||
/// `.template.dart` file.
|
|
||||||
/// 4. Chains a future to the `loadLibrary` call which initializes the
|
|
||||||
/// library.
|
|
||||||
///
|
|
||||||
/// To the extent possible, this method does not change line numbers or
|
|
||||||
/// offsets in the provided code to facilitate debugging via source maps.
|
|
||||||
Future<String> rewrite() async {
|
|
||||||
var code = await _reader.readAsString(_entryPoint);
|
|
||||||
|
|
||||||
// If we can determine there are no deferred libraries, avoid parsing the
|
// Get imports that need rewriting.
|
||||||
// entire file and bail early.
|
final deferredImports = await importVisitor.process();
|
||||||
var onlyDirectives = parseDirectives(code, name: _entryPoint.path);
|
if (deferredImports.isEmpty) {
|
||||||
if (onlyDirectives == null) return null;
|
log.fine('There are no deferred library imports that need rewriting.',
|
||||||
onlyDirectives.directives.accept(_visitor);
|
asset: entryPoint);
|
||||||
if (_visitor.deferredImports.isEmpty) return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var node = parseCompilationUnit(code, name: _entryPoint.path);
|
var node = parseCompilationUnit(code, name: entryPoint.path);
|
||||||
if (node == null) return null;
|
if (node == null) {
|
||||||
|
log.fine('No declarations parsed, bailing early.', asset: entryPoint);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return logElapsedAsync(() async {
|
final declarationsVisitor = new _FindLoadLibraryCalls(deferredImports);
|
||||||
node.declarations.accept(_visitor);
|
node.declarations.accept(declarationsVisitor);
|
||||||
// Look to see if we found any deferred libraries
|
|
||||||
if (!_visitor.hasDeferredLibrariesToRewrite()) return null;
|
|
||||||
// Remove any libraries that don't need angular codegen.
|
|
||||||
await _visitor.cull();
|
|
||||||
// Check again if there are any deferred libraries.
|
|
||||||
if (!_visitor.hasDeferredLibrariesToRewrite()) return null;
|
|
||||||
|
|
||||||
var compare = (AstNode a, AstNode b) => a.offset - b.offset;
|
// Get libraries that need rewriting.
|
||||||
_visitor.deferredImports.sort(compare);
|
if (declarationsVisitor.loadLibraryInvocations.isEmpty) {
|
||||||
_visitor.loadLibraryInvocations.sort(compare);
|
log.fine(
|
||||||
|
'There are no loadLibrary invocations that need to be rewritten.',
|
||||||
|
asset: entryPoint);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var buf = new StringBuffer();
|
return _rewriteLibrary(
|
||||||
var idx =
|
code, deferredImports, declarationsVisitor.loadLibraryInvocations);
|
||||||
_visitor.deferredImports.fold(0, (int lastIdx, ImportDirective node) {
|
}, operationName: 'rewriteDeferredLibraries', assetId: entryPoint);
|
||||||
// Write from where we left off until the start of the import uri.
|
|
||||||
buf.write(code.substring(lastIdx, node.uri.offset));
|
|
||||||
// Rewrite the uri to be that of the generated file.
|
|
||||||
buf.write("'${toTemplateExtension('${node.uri.stringValue}')}'");
|
|
||||||
// Update the last index we've processed.
|
|
||||||
return node.uri.end;
|
|
||||||
});
|
|
||||||
|
|
||||||
idx = _visitor.loadLibraryInvocations.fold(idx,
|
|
||||||
(int lastIdx, MethodInvocation node) {
|
|
||||||
buf.write(code.substring(lastIdx, node.offset));
|
|
||||||
var value = node.realTarget as SimpleIdentifier;
|
|
||||||
var prefix = value.name;
|
|
||||||
// Chain a future that initializes the reflector.
|
|
||||||
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
|
|
||||||
return node.end;
|
|
||||||
});
|
|
||||||
if (idx < code.length) buf.write(code.substring(idx));
|
|
||||||
return '$buf';
|
|
||||||
}, operationName: 'rewriteDeferredLibraries', assetId: _entryPoint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Visitor responsible for finding the deferred libraries that need angular
|
/// Rewrites the original [code] to initialize deferred libraries prior to use.
|
||||||
/// codegen. Those are the libraries that are loaded deferred and have a
|
///
|
||||||
/// corresponding generated file.
|
/// Note: This method may modify the order of [imports] and [loadLibCalls].
|
||||||
class _FindDeferredLibraries extends Object with RecursiveAstVisitor<Object> {
|
String _rewriteLibrary(String code, List<ImportDirective> imports,
|
||||||
var deferredImports = new List<ImportDirective>();
|
List<MethodInvocation> loadLibCalls) {
|
||||||
var loadLibraryInvocations = new List<MethodInvocation>();
|
/// Compares two [AstNode]s by position in the source code.
|
||||||
final AssetReader _reader;
|
var _compareNodes = (AstNode a, AstNode b) => a.offset - b.offset;
|
||||||
final AssetId _entryPoint;
|
|
||||||
|
// Necessary for indexes into [code] to function.
|
||||||
|
imports.sort(_compareNodes);
|
||||||
|
loadLibCalls.sort(_compareNodes);
|
||||||
|
|
||||||
|
var buf = new StringBuffer();
|
||||||
|
var idx = imports.fold(0, (int lastIdx, ImportDirective node) {
|
||||||
|
// Write from where we left off until the start of the import uri.
|
||||||
|
buf.write(code.substring(lastIdx, node.uri.offset));
|
||||||
|
// Rewrite the uri to be that of the generated file.
|
||||||
|
buf.write("'${toTemplateExtension('${node.uri.stringValue}')}'");
|
||||||
|
// Update the last index we've processed.
|
||||||
|
return node.uri.end;
|
||||||
|
});
|
||||||
|
|
||||||
|
idx = loadLibCalls.fold(idx, (int lastIdx, MethodInvocation node) {
|
||||||
|
buf.write(code.substring(lastIdx, node.offset));
|
||||||
|
var prefix = (node.realTarget as SimpleIdentifier).name;
|
||||||
|
// Chain a future that initializes the reflector.
|
||||||
|
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
|
||||||
|
return node.end;
|
||||||
|
});
|
||||||
|
if (idx < code.length) buf.write(code.substring(idx));
|
||||||
|
return '$buf';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds all `deferred` [ImportDirectives]s in an Ast that require init.
|
||||||
|
///
|
||||||
|
/// Use this to visit all [ImportDirective]s, then call [process] to get only
|
||||||
|
/// those [ImportDirective]s which are `deferred` and need Angular 2
|
||||||
|
/// initialization before use.
|
||||||
|
class _FindDeferredLibraries extends Object with SimpleAstVisitor<Object> {
|
||||||
|
final _deferredImports = <ImportDirective>[];
|
||||||
final _urlResolver = const TransformerUrlResolver();
|
final _urlResolver = const TransformerUrlResolver();
|
||||||
|
|
||||||
_FindDeferredLibraries(this._reader, this._entryPoint);
|
final AssetReader _reader;
|
||||||
|
final AssetId _entryPoint;
|
||||||
|
final String _entryPointUri;
|
||||||
|
|
||||||
|
_FindDeferredLibraries(this._reader, AssetId entryPoint)
|
||||||
|
: _entryPoint = entryPoint,
|
||||||
|
_entryPointUri = toAssetUri(entryPoint);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitImportDirective(ImportDirective node) {
|
Object visitImportDirective(ImportDirective node) {
|
||||||
if (node.deferredKeyword != null) {
|
if (node.deferredKeyword != null) {
|
||||||
deferredImports.add(node);
|
_deferredImports.add(node);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Gets the [AssetId] for the .ng_meta.json file associated with [import].
|
||||||
Object visitMethodInvocation(MethodInvocation node) {
|
AssetId _getAssociatedMetaAsset(ImportDirective import) {
|
||||||
if (node.methodName.name == 'loadLibrary') {
|
final importUri = stringLiteralToString(import.uri);
|
||||||
loadLibraryInvocations.add(node);
|
final associatedMetaUri = toMetaExtension(importUri);
|
||||||
}
|
return fromUri(_urlResolver.resolve(_entryPointUri, associatedMetaUri));
|
||||||
return super.visitMethodInvocation(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasDeferredLibrariesToRewrite() {
|
/// Gets a list of `deferred` [ImportDirective]s which need init.
|
||||||
if (deferredImports.isEmpty) {
|
///
|
||||||
log.fine('There are no deferred library imports.', asset: _entryPoint);
|
/// Removes all [ImportDirective]s from [_deferredImports] without an
|
||||||
return false;
|
/// associated .ng_meta.json file.
|
||||||
}
|
Future<List<ImportDirective>> process() async {
|
||||||
if (loadLibraryInvocations.isEmpty) {
|
// Parallel array with whether the input has an associated .ng_meta.json
|
||||||
log.fine(
|
// file.
|
||||||
'There are no loadLibrary invocations that need to be rewritten.',
|
final List<bool> hasInputs = await Future.wait(
|
||||||
asset: _entryPoint);
|
_deferredImports.map(_getAssociatedMetaAsset).map(_reader.hasInput));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all deferredImports that do not have an associated ng_meta file
|
|
||||||
// then remove all loadLibrary invocations that are not in the set of
|
|
||||||
// prefixes that are left.
|
|
||||||
Future cull() async {
|
|
||||||
var baseUri = toAssetUri(_entryPoint);
|
|
||||||
|
|
||||||
// Determine whether a deferred import has an associated generated file.
|
|
||||||
var hasInputs = await Future.wait(deferredImports
|
|
||||||
.map((import) => stringLiteralToString(import.uri))
|
|
||||||
.map((uri) => toMetaExtension(uri))
|
|
||||||
.map((metaUri) => fromUri(_urlResolver.resolve(baseUri, metaUri)))
|
|
||||||
.map((asset) => _reader.hasInput(asset)));
|
|
||||||
|
|
||||||
// Filter out any deferred imports that do not have an associated generated
|
// Filter out any deferred imports that do not have an associated generated
|
||||||
// file.
|
// file.
|
||||||
deferredImports = it.zip([deferredImports, hasInputs])
|
// Iteration order is important!
|
||||||
.where((importHasInput) => importHasInput[1])
|
for (var i = _deferredImports.length - 1; i >= 0; --i) {
|
||||||
.map((importHasInput) => importHasInput[0])
|
if (!hasInputs[i]) {
|
||||||
.toList();
|
_deferredImports.removeAt(i);
|
||||||
|
}
|
||||||
// Find the set of prefixes which have associated generated files.
|
}
|
||||||
var prefixes =
|
return _deferredImports;
|
||||||
new Set.from(deferredImports.map((import) => import.prefix.name));
|
}
|
||||||
|
}
|
||||||
// Filters out any load library invocations where the prefix is not a known
|
|
||||||
// library with associated generated file.
|
/// Finds all `loadLibrary` calls in the Ast that require init.
|
||||||
loadLibraryInvocations = loadLibraryInvocations.where((library) {
|
class _FindLoadLibraryCalls extends Object with RecursiveAstVisitor<Object> {
|
||||||
var value = library.realTarget as SimpleIdentifier;
|
/// The prefixes used by all `deferred` [ImportDirective]s that need init.
|
||||||
return prefixes.contains(value.name);
|
final Set _deferredPrefixes;
|
||||||
}).toList();
|
final loadLibraryInvocations = <MethodInvocation>[];
|
||||||
|
|
||||||
return;
|
_FindLoadLibraryCalls(List<ImportDirective> deferredImports)
|
||||||
|
: _deferredPrefixes =
|
||||||
|
new Set.from(deferredImports.map((import) => import.prefix.name));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitMethodInvocation(MethodInvocation node) {
|
||||||
|
if (node.methodName.name == 'loadLibrary') {
|
||||||
|
var prefix = (node.realTarget as SimpleIdentifier).name;
|
||||||
|
if (_deferredPrefixes.contains(prefix)) {
|
||||||
|
loadLibraryInvocations.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Important! Children could include more `loadLibrary` calls.
|
||||||
|
return super.visitMethodInvocation(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,5 @@ class DeferredRewriter extends AggregateTransformer implements LazyTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing
|
// Visible for testing
|
||||||
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) async {
|
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) =>
|
||||||
var rewriter = new Rewriter(id, reader);
|
rewriteLibrary(id, reader);
|
||||||
return await rewriter.rewrite();
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue