@@ -4,160 +4,176 @@ import 'dart:async';
44
55import 'package:analyzer/analyzer.dart' ;
66import 'package:analyzer/src/generated/ast.dart' ;
7+ import 'package:barback/barback.dart' ;
8+
79import 'package:angular2/src/transform/common/asset_reader.dart' ;
810import 'package:angular2/src/transform/common/logging.dart' ;
911import 'package:angular2/src/transform/common/names.dart' ;
1012import '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}
0 commit comments