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

Side by Side Diff: lib/transformer.dart

Issue 906413002: support multiple entry points and only html entry points (Closed) Base URL: git@github.com:dart-lang/static-init.git@master
Patch Set: add $include to transformer to optimize it a bit Created 5 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 unified diff | Download patch
« no previous file with comments | « README.md ('k') | pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 library initialize.transformer; 4 library initialize.transformer;
5 5
6 import 'dart:async'; 6 import 'dart:async';
7 import 'dart:collection' show Queue; 7 import 'dart:collection' show Queue;
8 import 'package:analyzer/src/generated/ast.dart'; 8 import 'package:analyzer/src/generated/ast.dart';
9 import 'package:analyzer/src/generated/element.dart'; 9 import 'package:analyzer/src/generated/element.dart';
10 import 'package:barback/barback.dart'; 10 import 'package:barback/barback.dart';
11 import 'package:code_transformers/assets.dart';
11 import 'package:code_transformers/resolver.dart'; 12 import 'package:code_transformers/resolver.dart';
13 import 'package:glob/glob.dart';
12 import 'package:html5lib/dom.dart' as dom; 14 import 'package:html5lib/dom.dart' as dom;
13 import 'package:html5lib/parser.dart' show parse; 15 import 'package:html5lib/parser.dart' show parse;
14 import 'package:path/path.dart' as path; 16 import 'package:path/path.dart' as path;
15 17
16 /// Removes the mirror-based initialization logic and replaces it with static 18 /// Removes the mirror-based initialization logic and replaces it with static
17 /// logic. 19 /// logic.
18 class InitializeTransformer extends Transformer { 20 class InitializeTransformer extends Transformer {
19 final Resolvers _resolvers; 21 final Resolvers _resolvers;
20 final String _entryPoint; 22 final Iterable<Glob> _entryPointGlobs;
21 final String _newEntryPoint; 23 final bool _errorIfNotFound;
22 final String _htmlEntryPoint;
23 24
24 InitializeTransformer( 25 InitializeTransformer(List<String> entryPoints, {bool errorIfNotFound: true})
25 this._entryPoint, this._newEntryPoint, this._htmlEntryPoint) 26 : _entryPointGlobs = entryPoints.map((e) => new Glob(e)),
26 : _resolvers = new Resolvers.fromMock({ 27 _errorIfNotFound = errorIfNotFound,
27 // The list of types below is derived from: 28 _resolvers = new Resolvers.fromMock({
28 // * types that are used internally by the resolver (see 29 // The list of types below is derived from:
29 // _initializeFrom in resolver.dart). 30 // * types that are used internally by the resolver (see
30 // TODO(jakemac): Move this into code_transformers so it can be shared. 31 // _initializeFrom in resolver.dart).
31 'dart:core': ''' 32 // TODO(jakemac): Move this into code_transformers so it can be shared .
33 'dart:core': '''
32 library dart.core; 34 library dart.core;
33 class Object {} 35 class Object {}
34 class Function {} 36 class Function {}
35 class StackTrace {} 37 class StackTrace {}
36 class Symbol {} 38 class Symbol {}
37 class Type {} 39 class Type {}
38 40
39 class String extends Object {} 41 class String extends Object {}
40 class bool extends Object {} 42 class bool extends Object {}
41 class num extends Object {} 43 class num extends Object {}
42 class int extends num {} 44 class int extends num {}
43 class double extends num {} 45 class double extends num {}
44 class DateTime extends Object {} 46 class DateTime extends Object {}
45 class Null extends Object {} 47 class Null extends Object {}
46 48
47 class Deprecated extends Object { 49 class Deprecated extends Object {
48 final String expires; 50 final String expires;
49 const Deprecated(this.expires); 51 const Deprecated(this.expires);
50 } 52 }
51 const Object deprecated = const Deprecated("next release"); 53 const Object deprecated = const Deprecated("next release");
52 class _Override { const _Override(); } 54 class _Override { const _Override(); }
53 const Object override = const _Override(); 55 const Object override = const _Override();
54 class _Proxy { const _Proxy(); } 56 class _Proxy { const _Proxy(); }
55 const Object proxy = const _Proxy(); 57 const Object proxy = const _Proxy();
56 58
57 class List<V> extends Object {} 59 class List<V> extends Object {}
58 class Map<K, V> extends Object {} 60 class Map<K, V> extends Object {}
59 ''', 61 ''',
60 'dart:html': ''' 62 'dart:html': '''
61 library dart.html; 63 library dart.html;
62 class HtmlElement {} 64 class HtmlElement {}
63 ''', 65 ''',
66 });
67
68 factory InitializeTransformer.asPlugin(BarbackSettings settings) =>
69 new InitializeTransformer(_readFileList(settings, 'entry_points'));
70
71 bool isPrimary(AssetId id) => _entryPointGlobs.any((g) => g.matches(id.path));
72
73 Future apply(Transform transform) {
74 if (transform.primaryInput.id.path.endsWith('.dart')) {
75 return _buildBootstrapFile(transform);
76 } else if (transform.primaryInput.id.path.endsWith('.html')) {
77 return transform.primaryInput.readAsString().then((html) {
78 var document = parse(html);
79 var originalDartFile =
80 _findMainScript(document, transform.primaryInput.id, transform);
81 return _buildBootstrapFile(transform, primaryId: originalDartFile).then(
82 (AssetId newDartFile) {
83 return _replaceEntryWithBootstrap(transform, document,
84 transform.primaryInput.id, originalDartFile, newDartFile);
85 });
64 }); 86 });
65 87 } else {
66 factory InitializeTransformer.asPlugin(BarbackSettings settings) { 88 transform.logger.warning(
67 var entryPoint = settings.configuration['entry_point']; 89 'Invalid entry point ${transform.primaryInput.id}. Must be either a '
68 var newEntryPoint = settings.configuration['new_entry_point']; 90 '.dart or .html file.');
69 if (newEntryPoint == null) {
70 newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart');
71 } 91 }
72 var htmlEntryPoint = settings.configuration['html_entry_point']; 92 return new Future.value();
73 return new InitializeTransformer(entryPoint, newEntryPoint, htmlEntryPoint);
74 } 93 }
75 94
76 bool isPrimary(AssetId id) => 95 // Returns the AssetId of the newly created bootstrap file.
77 _entryPoint == id.path || _htmlEntryPoint == id.path; 96 Future<AssetId> _buildBootstrapFile(Transform transform,
78 97 {AssetId primaryId}) {
79 Future apply(Transform transform) { 98 if (primaryId == null) primaryId = transform.primaryInput.id;
80 if (transform.primaryInput.id.path == _entryPoint) { 99 var newEntryPointId = new AssetId(primaryId.package,
81 return _buildBootstrapFile(transform); 100 '${path.url.withoutExtension(primaryId.path)}.initialize.dart');
82 } else if (transform.primaryInput.id.path == _htmlEntryPoint) {
83 return _replaceEntryWithBootstrap(transform);
84 }
85 return null;
86 }
87
88 Future _buildBootstrapFile(Transform transform) {
89 var newEntryPointId =
90 new AssetId(transform.primaryInput.id.package, _newEntryPoint);
91 return transform.hasInput(newEntryPointId).then((exists) { 101 return transform.hasInput(newEntryPointId).then((exists) {
92 if (exists) { 102 if (exists) {
93 transform.logger 103 transform.logger
94 .error('New entry point file $newEntryPointId already exists.'); 104 .error('New entry point file $newEntryPointId already exists.');
95 } else { 105 return null;
96 return _resolvers.get(transform).then((resolver) {
97 new _BootstrapFileBuilder(resolver, transform,
98 transform.primaryInput.id, newEntryPointId).run();
99 resolver.release();
100 });
101 } 106 }
107
108 return _resolvers.get(transform, [primaryId]).then((resolver) {
109 new _BootstrapFileBuilder(resolver, transform, primaryId,
110 newEntryPointId, _errorIfNotFound).run();
111 resolver.release();
112 return newEntryPointId;
113 });
102 }); 114 });
103 } 115 }
104 116
105 Future _replaceEntryWithBootstrap(Transform transform) { 117 // Replaces script tags pointing to [originalDartFile] with [newDartFile] in
106 // For now at least, _htmlEntryPoint, _entryPoint, and _newEntryPoint need 118 // [entryPoint].
107 // to be in the same folder. 119 void _replaceEntryWithBootstrap(Transform transform, dom.Document document,
108 // TODO(jakemac): support package urls with _entryPoint or _newEntryPoint 120 AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) {
109 // in `lib`, and _htmlEntryPoint in another directory. 121 var found = false;
110 var _expectedDir = path.split(_htmlEntryPoint)[0]; 122 var scripts = document
111 if (_expectedDir != path.split(_entryPoint)[0] || 123 .querySelectorAll('script[type="application/dart"]')
112 _expectedDir != path.split(_newEntryPoint)[0]) { 124 .where((script) => uriToAssetId(entryPoint, script.attributes['src'],
125 transform.logger, script.sourceSpan) == originalDartFile)
126 .toList();
127
128 if (scripts.length != 1) {
113 transform.logger.error( 129 transform.logger.error(
114 'htmlEntryPoint, entryPoint, and newEntryPoint(if supplied) all must ' 130 'Expected exactly one script pointing to $originalDartFile in '
115 'be in the same top level directory.'); 131 '$entryPoint, but found ${scripts.length}.');
132 return;
133 }
134 scripts[0].attributes['src'] = path.url.relative(newDartFile.path,
135 from: path.dirname(entryPoint.path));
136 transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml));
137 }
138
139 AssetId _findMainScript(
140 dom.Document document, AssetId entryPoint, Transform transform) {
141 var scripts = document.querySelectorAll('script[type="application/dart"]');
142 if (scripts.length != 1) {
143 transform.logger.error('Expected exactly one dart script in $entryPoint '
144 'but found ${scripts.length}.');
145 return null;
116 } 146 }
117 147
118 return transform.primaryInput.readAsString().then((String html) { 148 var src = scripts[0].attributes['src'];
119 var found = false; 149 // TODO(jakemac): Support inline scripts,
120 var doc = parse(html); 150 // https://github.com/dart-lang/initialize/issues/20
121 var scripts = doc.querySelectorAll('script[type="application/dart"]'); 151 if (src == null) {
122 for (dom.Element script in scripts) { 152 transform.logger.error('Inline scripts are not supported at this time.');
123 if (!_isEntryPointScript(script)) continue; 153 return null;
124 script.attributes['src'] = _relativeDartEntryPath(_newEntryPoint); 154 }
125 found = true; 155
126 } 156 return uriToAssetId(
127 if (!found) { 157 entryPoint, src, transform.logger, scripts[0].sourceSpan);
128 transform.logger.error(
129 'Unable to find script for $_entryPoint in $_htmlEntryPoint.');
130 }
131 return transform.addOutput(
132 new Asset.fromString(transform.primaryInput.id, doc.outerHtml));
133 });
134 } 158 }
135
136 // Checks if the src of this script tag is pointing at `_entryPoint`.
137 bool _isEntryPointScript(dom.Element script) =>
138 path.normalize(script.attributes['src']) ==
139 _relativeDartEntryPath(_entryPoint);
140
141 // The relative path from `_htmlEntryPoint` to `dartEntry`. You must ensure
142 // that neither of these is null before calling this function.
143 String _relativeDartEntryPath(String dartEntry) =>
144 path.relative(dartEntry, from: path.dirname(_htmlEntryPoint));
145 } 159 }
146 160
147 class _BootstrapFileBuilder { 161 class _BootstrapFileBuilder {
148 final Resolver _resolver; 162 final Resolver _resolver;
149 final Transform _transform; 163 final Transform _transform;
164 final bool _errorIfNotFound;
150 AssetId _entryPoint; 165 AssetId _entryPoint;
151 AssetId _newEntryPoint; 166 AssetId _newEntryPoint;
152 167
153 /// The resolved initialize library. 168 /// The resolved initialize library.
154 LibraryElement _initializeLibrary; 169 LibraryElement _initializeLibrary;
155 /// The resolved Initializer class from the initialize library. 170 /// The resolved Initializer class from the initialize library.
156 ClassElement _initializer; 171 ClassElement _initializer;
157 172
158 /// Queue for intialization annotations. 173 /// Queue for intialization annotations.
159 final _initQueue = new Queue<_InitializerData>(); 174 final _initQueue = new Queue<_InitializerData>();
160 /// All the annotations we have seen for each element 175 /// All the annotations we have seen for each element
161 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>(); 176 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>();
162 177
163 TransformLogger _logger; 178 TransformLogger _logger;
164 179
165 _BootstrapFileBuilder( 180 _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint,
166 this._resolver, this._transform, this._entryPoint, this._newEntryPoint) { 181 this._newEntryPoint, this._errorIfNotFound) {
167 _logger = _transform.logger; 182 _logger = _transform.logger;
168 _initializeLibrary = 183 _initializeLibrary =
169 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); 184 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart'));
170 _initializer = _initializeLibrary.getType('Initializer'); 185 if (_initializeLibrary != null) {
186 _initializer = _initializeLibrary.getType('Initializer');
187 } else if (_errorIfNotFound) {
188 _logger.warning('Unable to read "package:initialize/initialize.dart". '
189 'This file must be imported via $_entryPoint or a transitive '
190 'dependency.');
191 }
171 } 192 }
172 193
173 /// Adds the new entry point file to the transform. Should only be ran once. 194 /// Adds the new entry point file to the transform. Should only be ran once.
174 void run() { 195 void run() {
175 var entryLib = _resolver.getLibrary(_entryPoint); 196 var entryLib = _resolver.getLibrary(_entryPoint);
176 _readLibraries(entryLib); 197 _readLibraries(entryLib);
177 198
178 _transform.addOutput( 199 _transform.addOutput(
179 new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib))); 200 new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib)));
180 } 201 }
(...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after
445 _logger.error('Unsupported argument to initializer constructor.'); 466 _logger.error('Unsupported argument to initializer constructor.');
446 } 467 }
447 } else { 468 } else {
448 _logger.error('Only literals and identifiers are allowed for initializer ' 469 _logger.error('Only literals and identifiers are allowed for initializer '
449 'expressions, found $expression.'); 470 'expressions, found $expression.');
450 } 471 }
451 return buffer.toString(); 472 return buffer.toString();
452 } 473 }
453 474
454 bool _isInitializer(InterfaceType type) { 475 bool _isInitializer(InterfaceType type) {
476 // If `_initializer` wasn't found then it was never loaded (even
477 // transitively), and so no annotations can be initializers.
478 if (_initializer == null) return false;
455 if (type == null) return false; 479 if (type == null) return false;
456 if (type.element.type == _initializer.type) return true; 480 if (type.element.type == _initializer.type) return true;
457 if (_isInitializer(type.superclass)) return true; 481 if (_isInitializer(type.superclass)) return true;
458 for (var interface in type.interfaces) { 482 for (var interface in type.interfaces) {
459 if (_isInitializer(interface)) return true; 483 if (_isInitializer(interface)) return true;
460 } 484 }
461 return false; 485 return false;
462 } 486 }
463 487
464 /// Retrieves all top-level methods that are visible if you were to import 488 /// Retrieves all top-level methods that are visible if you were to import
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 })).map((import) => import.importedLibrary); 561 })).map((import) => import.importedLibrary);
538 } 562 }
539 563
540 // Element/ElementAnnotation pair. 564 // Element/ElementAnnotation pair.
541 class _InitializerData { 565 class _InitializerData {
542 final Element element; 566 final Element element;
543 final ElementAnnotation annotation; 567 final ElementAnnotation annotation;
544 568
545 _InitializerData(this.element, this.annotation); 569 _InitializerData(this.element, this.annotation);
546 } 570 }
571
572 // Reads a file list from a barback settings configuration field.
573 _readFileList(BarbackSettings settings, String field) {
574 var value = settings.configuration[field];
575 if (value == null) return null;
576 var files = [];
577 bool error;
578 if (value is List) {
579 files = value;
580 error = value.any((e) => e is! String);
581 } else if (value is String) {
582 files = [value];
583 error = false;
584 } else {
585 error = true;
586 }
587 if (error) {
588 print('Bad value for "$field" in the initialize transformer. '
589 'Expected either one String or a list of Strings.');
590 }
591 return files;
592 }
OLDNEW
« no previous file with comments | « README.md ('k') | pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698