X Tutup
Skip to content

Commit 1fd924f

Browse files
Tim Blasikegluneq
authored andcommitted
refactor(dart/transform): Simplify deferred rewriting
Simplify the DeferredRewriting and move package:quiver to a dev dependency. Closes angular#6994
1 parent eb688f2 commit 1fd924f

File tree

3 files changed

+147
-133
lines changed

3 files changed

+147
-133
lines changed

modules/angular2/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ dependencies:
1818
logging: '>=0.9.0 <0.12.0'
1919
observe: '^0.13.1'
2020
protobuf: '^0.5.0'
21-
quiver: '^0.21.4'
2221
source_span: '^1.0.0'
2322
stack_trace: '^1.1.1'
2423
dev_dependencies:
2524
code_transformers: '>=0.2.9+4 <0.4.0'
2625
guinness: '^0.1.18'
26+
quiver: '^0.21.4'
2727
test: '^0.12.6'
2828
transformers:
2929
- angular2

modules_dart/transform/lib/src/transform/deferred_rewriter/rewriter.dart

Lines changed: 144 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -4,160 +4,176 @@ import 'dart:async';
44

55
import 'package:analyzer/analyzer.dart';
66
import 'package:analyzer/src/generated/ast.dart';
7+
import 'package:barback/barback.dart';
8+
79
import 'package:angular2/src/transform/common/asset_reader.dart';
810
import 'package:angular2/src/transform/common/logging.dart';
911
import 'package:angular2/src/transform/common/names.dart';
1012
import 'package:angular2/src/transform/common/url_resolver.dart';
11-
import 'package:barback/barback.dart';
12-
import 'package:quiver/iterables.dart' as it;
1313

14-
class Rewriter {
15-
final AssetId _entryPoint;
16-
final AssetReader _reader;
17-
final _FindDeferredLibraries _visitor;
14+
/// Rewrites `loadLibrary` calls to initialize libraries once loaded.
15+
///
16+
/// 1. Finds all the deferred library imports and loadLibrary invocations in
17+
/// `_entryPoint`
18+
/// 2. Removes any libraries that don't require angular codegen.
19+
/// 3. For the remaining libraries, rewrites the import to the corresponding
20+
/// `.template.dart` file.
21+
/// 4. Chains a future to the `loadLibrary` call which initializes the
22+
/// library.
23+
///
24+
/// To the extent possible, this method does not change line numbers or
25+
/// offsets in the provided code to facilitate debugging via source maps.
26+
Future<String> rewriteLibrary(AssetId entryPoint, AssetReader reader) async {
27+
var code = await reader.readAsString(entryPoint);
28+
29+
return logElapsedAsync(() async {
30+
// If we can determine there are no deferred libraries, avoid additional
31+
// parsing the entire file and bail early.
32+
var onlyDirectives = parseDirectives(code, name: entryPoint.path);
33+
if (onlyDirectives == null) {
34+
log.fine('No directives parsed, bailing early.', asset: entryPoint);
35+
return null;
36+
}
1837

19-
Rewriter(AssetId entryPoint, AssetReader reader)
20-
: _entryPoint = entryPoint,
21-
_reader = reader,
22-
_visitor = new _FindDeferredLibraries(reader, entryPoint);
38+
final importVisitor = new _FindDeferredLibraries(reader, entryPoint);
39+
onlyDirectives.directives.accept(importVisitor);
2340

24-
/// Rewrites `loadLibrary` calls to initialize libraries once loaded.
25-
///
26-
/// 1. Finds all the deferred library imports and loadLibrary invocations in
27-
/// `_entryPoint`
28-
/// 2. Removes any libraries that don't require angular codegen.
29-
/// 3. For the remaining libraries, rewrites the import to the corresponding
30-
/// `.template.dart` file.
31-
/// 4. Chains a future to the `loadLibrary` call which initializes the
32-
/// library.
33-
///
34-
/// To the extent possible, this method does not change line numbers or
35-
/// offsets in the provided code to facilitate debugging via source maps.
36-
Future<String> rewrite() async {
37-
var code = await _reader.readAsString(_entryPoint);
38-
39-
// If we can determine there are no deferred libraries, avoid parsing the
40-
// entire file and bail early.
41-
var onlyDirectives = parseDirectives(code, name: _entryPoint.path);
42-
if (onlyDirectives == null) return null;
43-
onlyDirectives.directives.accept(_visitor);
44-
if (_visitor.deferredImports.isEmpty) return null;
45-
46-
var node = parseCompilationUnit(code, name: _entryPoint.path);
47-
if (node == null) return null;
48-
49-
return logElapsedAsync(() async {
50-
node.declarations.accept(_visitor);
51-
// Look to see if we found any deferred libraries
52-
if (!_visitor.hasDeferredLibrariesToRewrite()) return null;
53-
// Remove any libraries that don't need angular codegen.
54-
await _visitor.cull();
55-
// Check again if there are any deferred libraries.
56-
if (!_visitor.hasDeferredLibrariesToRewrite()) return null;
57-
58-
var compare = (AstNode a, AstNode b) => a.offset - b.offset;
59-
_visitor.deferredImports.sort(compare);
60-
_visitor.loadLibraryInvocations.sort(compare);
61-
62-
var buf = new StringBuffer();
63-
var idx =
64-
_visitor.deferredImports.fold(0, (int lastIdx, ImportDirective node) {
65-
// Write from where we left off until the start of the import uri.
66-
buf.write(code.substring(lastIdx, node.uri.offset));
67-
// Rewrite the uri to be that of the generated file.
68-
buf.write("'${toTemplateExtension('${node.uri.stringValue}')}'");
69-
// Update the last index we've processed.
70-
return node.uri.end;
71-
});
72-
73-
idx = _visitor.loadLibraryInvocations.fold(idx,
74-
(int lastIdx, MethodInvocation node) {
75-
buf.write(code.substring(lastIdx, node.offset));
76-
var value = node.realTarget as SimpleIdentifier;
77-
var prefix = value.name;
78-
// Chain a future that initializes the reflector.
79-
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
80-
return node.end;
81-
});
82-
if (idx < code.length) buf.write(code.substring(idx));
83-
return '$buf';
84-
}, operationName: 'rewriteDeferredLibraries', assetId: _entryPoint);
85-
}
41+
// Get imports that need rewriting.
42+
final deferredImports = await importVisitor.process();
43+
if (deferredImports.isEmpty) {
44+
log.fine('There are no deferred library imports that need rewriting.',
45+
asset: entryPoint);
46+
return null;
47+
}
48+
49+
var node = parseCompilationUnit(code, name: entryPoint.path);
50+
if (node == null) {
51+
log.fine('No declarations parsed, bailing early.', asset: entryPoint);
52+
return null;
53+
}
54+
55+
final declarationsVisitor = new _FindLoadLibraryCalls(deferredImports);
56+
node.declarations.accept(declarationsVisitor);
57+
58+
// Get libraries that need rewriting.
59+
if (declarationsVisitor.loadLibraryInvocations.isEmpty) {
60+
log.fine(
61+
'There are no loadLibrary invocations that need to be rewritten.',
62+
asset: entryPoint);
63+
return null;
64+
}
65+
66+
return _rewriteLibrary(
67+
code, deferredImports, declarationsVisitor.loadLibraryInvocations);
68+
}, operationName: 'rewriteDeferredLibraries', assetId: entryPoint);
69+
}
70+
71+
/// Rewrites the original [code] to initialize deferred libraries prior to use.
72+
///
73+
/// Note: This method may modify the order of [imports] and [loadLibCalls].
74+
String _rewriteLibrary(String code, List<ImportDirective> imports,
75+
List<MethodInvocation> loadLibCalls) {
76+
/// Compares two [AstNode]s by position in the source code.
77+
var _compareNodes = (AstNode a, AstNode b) => a.offset - b.offset;
78+
79+
// Necessary for indexes into [code] to function.
80+
imports.sort(_compareNodes);
81+
loadLibCalls.sort(_compareNodes);
82+
83+
var buf = new StringBuffer();
84+
var idx = imports.fold(0, (int lastIdx, ImportDirective node) {
85+
// Write from where we left off until the start of the import uri.
86+
buf.write(code.substring(lastIdx, node.uri.offset));
87+
// Rewrite the uri to be that of the generated file.
88+
buf.write("'${toTemplateExtension('${node.uri.stringValue}')}'");
89+
// Update the last index we've processed.
90+
return node.uri.end;
91+
});
92+
93+
idx = loadLibCalls.fold(idx, (int lastIdx, MethodInvocation node) {
94+
buf.write(code.substring(lastIdx, node.offset));
95+
var prefix = (node.realTarget as SimpleIdentifier).name;
96+
// Chain a future that initializes the reflector.
97+
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
98+
return node.end;
99+
});
100+
if (idx < code.length) buf.write(code.substring(idx));
101+
return '$buf';
86102
}
87103

88-
/// Visitor responsible for finding the deferred libraries that need angular
89-
/// codegen. Those are the libraries that are loaded deferred and have a
90-
/// corresponding generated file.
91-
class _FindDeferredLibraries extends Object with RecursiveAstVisitor<Object> {
92-
var deferredImports = new List<ImportDirective>();
93-
var loadLibraryInvocations = new List<MethodInvocation>();
104+
/// Finds all `deferred` [ImportDirectives]s in an Ast that require init.
105+
///
106+
/// Use this to visit all [ImportDirective]s, then call [process] to get only
107+
/// those [ImportDirective]s which are `deferred` and need Angular 2
108+
/// initialization before use.
109+
class _FindDeferredLibraries extends Object with SimpleAstVisitor<Object> {
110+
final _deferredImports = <ImportDirective>[];
111+
final _urlResolver = const TransformerUrlResolver();
112+
94113
final AssetReader _reader;
95114
final AssetId _entryPoint;
96-
final _urlResolver = const TransformerUrlResolver();
115+
final String _entryPointUri;
97116

98-
_FindDeferredLibraries(this._reader, this._entryPoint);
117+
_FindDeferredLibraries(this._reader, AssetId entryPoint)
118+
: _entryPoint = entryPoint,
119+
_entryPointUri = toAssetUri(entryPoint);
99120

100121
@override
101122
Object visitImportDirective(ImportDirective node) {
102123
if (node.deferredKeyword != null) {
103-
deferredImports.add(node);
124+
_deferredImports.add(node);
104125
}
105126
return null;
106127
}
107128

108-
@override
109-
Object visitMethodInvocation(MethodInvocation node) {
110-
if (node.methodName.name == 'loadLibrary') {
111-
loadLibraryInvocations.add(node);
112-
}
113-
return super.visitMethodInvocation(node);
129+
/// Gets the [AssetId] for the .ng_meta.json file associated with [import].
130+
AssetId _getAssociatedMetaAsset(ImportDirective import) {
131+
final importUri = stringLiteralToString(import.uri);
132+
final associatedMetaUri = toMetaExtension(importUri);
133+
return fromUri(_urlResolver.resolve(_entryPointUri, associatedMetaUri));
114134
}
115135

116-
bool hasDeferredLibrariesToRewrite() {
117-
if (deferredImports.isEmpty) {
118-
log.fine('There are no deferred library imports.', asset: _entryPoint);
119-
return false;
120-
}
121-
if (loadLibraryInvocations.isEmpty) {
122-
log.fine(
123-
'There are no loadLibrary invocations that need to be rewritten.',
124-
asset: _entryPoint);
125-
return false;
136+
/// Gets a list of `deferred` [ImportDirective]s which need init.
137+
///
138+
/// Removes all [ImportDirective]s from [_deferredImports] without an
139+
/// associated .ng_meta.json file.
140+
Future<List<ImportDirective>> process() async {
141+
// Parallel array with whether the input has an associated .ng_meta.json
142+
// file.
143+
final List<bool> hasInputs = await Future.wait(
144+
_deferredImports.map(_getAssociatedMetaAsset).map(_reader.hasInput));
145+
146+
// Filter out any deferred imports that do not have an associated generated
147+
// file.
148+
// Iteration order is important!
149+
for (var i = _deferredImports.length - 1; i >= 0; --i) {
150+
if (!hasInputs[i]) {
151+
_deferredImports.removeAt(i);
152+
}
126153
}
127-
return true;
154+
return _deferredImports;
128155
}
156+
}
129157

130-
// Remove all deferredImports that do not have an associated ng_meta file
131-
// then remove all loadLibrary invocations that are not in the set of
132-
// prefixes that are left.
133-
Future cull() async {
134-
var baseUri = toAssetUri(_entryPoint);
158+
/// Finds all `loadLibrary` calls in the Ast that require init.
159+
class _FindLoadLibraryCalls extends Object with RecursiveAstVisitor<Object> {
160+
/// The prefixes used by all `deferred` [ImportDirective]s that need init.
161+
final Set _deferredPrefixes;
162+
final loadLibraryInvocations = <MethodInvocation>[];
135163

136-
// Determine whether a deferred import has an associated generated file.
137-
var hasInputs = await Future.wait(deferredImports
138-
.map((import) => stringLiteralToString(import.uri))
139-
.map((uri) => toMetaExtension(uri))
140-
.map((metaUri) => fromUri(_urlResolver.resolve(baseUri, metaUri)))
141-
.map((asset) => _reader.hasInput(asset)));
164+
_FindLoadLibraryCalls(List<ImportDirective> deferredImports)
165+
: _deferredPrefixes =
166+
new Set.from(deferredImports.map((import) => import.prefix.name));
142167

143-
// Filter out any deferred imports that do not have an associated generated
144-
// file.
145-
deferredImports = it.zip([deferredImports, hasInputs])
146-
.where((importHasInput) => importHasInput[1])
147-
.map((importHasInput) => importHasInput[0])
148-
.toList();
149-
150-
// Find the set of prefixes which have associated generated files.
151-
var prefixes =
152-
new Set.from(deferredImports.map((import) => import.prefix.name));
153-
154-
// Filters out any load library invocations where the prefix is not a known
155-
// library with associated generated file.
156-
loadLibraryInvocations = loadLibraryInvocations.where((library) {
157-
var value = library.realTarget as SimpleIdentifier;
158-
return prefixes.contains(value.name);
159-
}).toList();
160-
161-
return;
168+
@override
169+
Object visitMethodInvocation(MethodInvocation node) {
170+
if (node.methodName.name == 'loadLibrary') {
171+
var prefix = (node.realTarget as SimpleIdentifier).name;
172+
if (_deferredPrefixes.contains(prefix)) {
173+
loadLibraryInvocations.add(node);
174+
}
175+
}
176+
// Important! Children could include more `loadLibrary` calls.
177+
return super.visitMethodInvocation(node);
162178
}
163179
}

modules_dart/transform/lib/src/transform/deferred_rewriter/transformer.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,5 @@ class DeferredRewriter extends AggregateTransformer implements LazyTransformer {
8181
}
8282

8383
// Visible for testing
84-
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) async {
85-
var rewriter = new Rewriter(id, reader);
86-
return await rewriter.rewrite();
87-
}
84+
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) =>
85+
rewriteLibrary(id, reader);

0 commit comments

Comments
 (0)
X Tutup